Struts2のActionとインターセプターのAOP部分を見る


前回のAOPの定番 Spring Frameworkの動的プロキシ部分を見るをふまえて、Struts2AOP部分をStruts 2.0.11のサンプル「blank」を例に見てみます。

サンプルの「blank」のweb.xmlを見ると、以下のように設定してありますので、リクエスト毎にorg.apache.struts2.dispatcher.FilterDispatcherのdoFilterメソッドが呼び出されるのがわかります。

    <filter>
        <filter-name>struts2</filter-name>
        <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
    </filter>


1).そのorg.apache.struts2.dispatcher.FilterDispatcher(419行目)#doFilter()では、以下の呼び出しがあります。

419  dispatcher.serviceAction(request, response, servletContext, mapping);

※FilterDispatcherはjavax.servlet.Filterを実装したクラスです。
2).このdispatcherは、org.apache.struts2.dispatcher.Dispatcherであり、そのorg.apache.struts2.dispatcher.Dispatcher(504行目)#serviceAction()では、以下の呼び出しがあります。

504  proxy.execute();

※serviceActionメソッドは、ActionProxyを生成し、ActionProxyのexecuteメソッドを実行します。
3).このproxyは、org.apache.struts2.impl.StrutsActionProxyであり、そのorg.apache.struts2.impl.StrutsActionProxy(52行目)#execute()では、以下の呼び出しがあります。

52  return invocation.invoke();

4).このinvocation.invoke()は、com.opensymphony.xwork2.DefaultActionInvocation(210行目)#invoke()であり、そのcom.opensymphony.xwork2.DefaultActionInvocation(224行目)#invoke()は、以下のように書かれています。

210    public String invoke() throws Exception {
211        String profileKey = "invoke: ";
212        try {
213          UtilTimerStack.push(profileKey);
214        
215          if (executed) {
216            throw new IllegalStateException("Action has already executed");
217          }
218        
219          if (interceptors.hasNext()) {
220            final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next();
221            UtilTimerStack.profile("interceptor: "+interceptor.getName(), 
222                  new UtilTimerStack.ProfilingBlock<String>() {
223	                   public String doProfiling() throws Exception {
224	                     resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
225	                     return null;
226	                   }
227            });
228          } else {
229            resultCode = invokeActionOnly();
230         }
          ・
          ・
          ・

この辺で、前回AOPの定番 Spring Frameworkの動的プロキシ部分を見ると同じようなロジックがでてきました。
上記の、224行目で自インスタンスを引数にインターセプターのinterceptメソッドを呼び出しています。


ここで呼び出されるインターセプターのサンプルは、org.apache.struts2.interceptor.ServletConfigInterceptorを見てみましょう。
org.apache.struts2.interceptor.ServletConfigInterceptor(125行目)#intercept()は、以下のように書かれています。

125    public String intercept(ActionInvocation invocation) throws Exception {
126      final Object action = invocation.getAction();
127      final ActionContext context = invocation.getInvocationContext();
128      
129      if (action instanceof ServletRequestAware) {
130          HttpServletRequest request = (HttpServletRequest) context.get(HTTP_REQUEST);
131          ((ServletRequestAware) action).setServletRequest(request);
132      }
133      
134      if (action instanceof ServletResponseAware) {
135          HttpServletResponse response = (HttpServletResponse) context.get(HTTP_RESPONSE);
136          ((ServletResponseAware) action).setServletResponse(response);
137      }
138      
139      if (action instanceof ParameterAware) {
140          ((ParameterAware) action).setParameters(context.getParameters());
141      }
142
143      if (action instanceof RequestAware) {
144          ((RequestAware) action).setRequest((Map) context.get("request"));
145      }
          ・
          ・
          ・
170        return invocation.invoke();
171    }


org.apache.struts2.interceptor.ServletConfigInterceptorの設定は、デフォルトでstruts-default.xmlに記述してあるので、特に設定なしでActionが動作する毎に動作します。
あとは前回AOPの定番 Spring Frameworkの動的プロキシ部分を見る同様、4).com.opensymphony.xwork2.DefaultActionInvocation#invokeメソッドは自インスタンスを引数にインターセプターのintercept()を呼び出す。
それぞれのインターセプターはintercept()の中で実行対象メソッドの前後処理を行い、引数で受け取ったDefaultActionInvocationのinvoke()を呼び出す。
つまり、invokeメソッドとinterceptメソッドが連鎖することになります。

このServletConfigInterceptor#interceptは、対象のActionクラスがどのインターフェイスを実装しているかによって、処理を行います。(ActionがRequestAwareを実装してれば、ActionのsetRequestメソッドでリクエストマップを設定するなど。)


Struts2もこの辺を把握しながら実装していけば、よりデバッグも簡単に行えるようになるかな〜と思います。