1. The realization principle of interceptor:
Most of the time, interceptor methods are invoked by proxy. Struts 2 interceptor is relatively simple to implement. When the request arrives at the Servlet Dispatcher of Struts 2, Struts 2 looks up the configuration file, instantiates the relative interceptor objects according to its configuration, and then strings them into a list, which calls the interceptors in the list one by one. In fact, we owe all our flexibility in using interceptors to the use of dynamic agents. Dynamic proxy is that the proxy objects make different processing according to the needs of customers. For customers, just know a proxy object. In Struts 2, how is the interceptor invoked through a dynamic proxy? When an Action request arrives, a proxy object of the Action is generated by the proxy of the system. The proxy object calls the execute() of the Action or the specified method, and finds the interceptor corresponding to the Action in struts.xml. If there are corresponding interceptors, they are called before (after) the execution of the Action method; if there are no corresponding interceptors, the Action method is executed. The system call to interceptor is realized by ActionInvocation. The code is as follows:
if (interceptors.hasNext()) { Interceptor interceptor=(Interceptor)interceptors.next(); resultCode = interceptor.intercept(this); } else { if (proxy.getConfig().getMethodName() == null) { resultCode = getAction().execute(); } else { resultCode = invokeAction(getAction(), proxy.getConfig()); } }
It can be found that the Action is not directly related to the interceptor, but is entirely a "proxy" that organizes the Action to work with the interceptor. The following picture:
II. Interceptor Execution Analysis
As we all know, there is nothing special about the interface definition of Interceptor. In addition to init and destory methods, intercept method is the core method to implement the whole interceptor mechanism. ActionInvocation, the parameter on which it depends, is a well-known Action scheduler. Let's look at a typical Abstract implementation class of Interceptor:
public abstract class AroundInterceptor extends AbstractInterceptor { /* (non-Javadoc) * @see com.opensymphony.xwork2.interceptor.AbstractInterceptor#intercept(com.opensymphony.xwork2.ActionInvocation) */ @Override public String intercept(ActionInvocation invocation) throws Exception { String result = null; before(invocation); // Call the next interceptor and execute Action if the interceptor does not exist result = invocation.invoke(); after(invocation, result); return result; } public abstract void before(ActionInvocation invocation) throws Exception; public abstract void after(ActionInvocation invocation, String resultCode) throws Exception; }
In this implementation class, the prototype of the simplest interceptor has actually been implemented. One important method to point out here is invocation.invoke(). This is the method in Action Invocation, which is the action scheduler, so this method has the following two meanings:
1. If there are other Interceptors in the interceptor stack, invocation.invoke() will call the next Interceptor execution in the stack.
2. If there is only Action in the interceptor stack, invocation.invoke() will call Action execution.
Therefore, we can find that invocation.invoke() is actually the core of the implementation of the interceptor framework. Based on this implementation mechanism, we can also get the following two very important inferences:
1. If in the interceptor, instead of invocation.invoke() to complete the call to the next element in the stack, we return a string directly as the result of execution, then the entire execution will be aborted.
2. We can divide the code in the interceptor into two parts with invocation. The code before invocation.invoke() will be executed before the Action, and the code after invocation.invoke() will be executed in reverse order after the Action.
Thus, we can implement AOP by using invocation.invoke() as the real interception point of Action code.
3. Source code parsing
Next, let's look at the source code to see how Struts 2 guarantees the execution order between the interceptor, Action and Result. As I mentioned earlier, ActionInvocation is the scheduler in Struts 2, so in fact, the scheduling of these codes is done in the implementation class of ActionInvocation. Here, I extract the invoke() method from DefaultActionInvocation, which will show us everything.
From the source code, we can see four different levels of the Action layer, which are embodied in this method: Interceptor, Action, PreResultListener and Result. In this method, orderly invocation and execution of these levels are guaranteed. From this we can also see that Struts 2 has many considerations in the action level design, each level has a high degree of scalability and insertion point, so that programmers can add their own implementation mechanism to change the action behavior at any level they like./** * @throws ConfigurationException If no result can be found with the returned code */ public String invoke() throws Exception { String profileKey = "invoke: "; try { UtilTimerStack.push(profileKey); if (executed) { throw new IllegalStateException("Action has already executed"); } // Call interceptor code execution in the interceptor stack in turn if (interceptors.hasNext()) { final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next(); UtilTimerStack.profile("interceptor: "+interceptor.getName(), new UtilTimerStack.ProfilingBlock<String>() { public String doProfiling() throws Exception { // Call the intercept method in interceptor to execute with ActionInvocation as a parameter resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this); return null; } }); } else { resultCode = invokeActionOnly(); } // this is needed because the result will be executed, then control will return to the Interceptor, which will // return above and flow through again if (!executed) { // Execute PreResultListener if (preResultListeners != null) { for (Iterator iterator = preResultListeners.iterator(); iterator.hasNext();) { PreResultListener listener = (PreResultListener) iterator.next(); String _profileKey="preResultListener: "; try { UtilTimerStack.push(_profileKey); listener.beforeResult(this, resultCode); } finally { UtilTimerStack.pop(_profileKey); } } } // now execute the result, if we're supposed to // action and interceptor are executed, Result is executed if (proxy.getExecuteResult()) { executeResult(); } executed = true; } return resultCode; } finally { UtilTimerStack.pop(profileKey); } }
Here, it is the execution call of the interceptor part that needs special emphasis:
On the surface, it only executes the intercept method in the interceptor. If we combine it with the interceptor, we can see the point and end of the interceptor.resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
public String intercept(ActionInvocation invocation) throws Exception { String result = null; before(invocation); // The invoke() method of invocation is called, where recursive calls are formed. result = invocation.invoke(); after(invocation, result); return result; }
Initially, the intercept() method recursively calls the invoke() method of ActionInvocation. The ActionInvocation loop is nested in intercept(), until the statement result = invocation.invoke() is executed. In this way, the Interceptor will execute in reverse order from the beginning. An ordered list, through recursive invocation, becomes a stack execution process, transforming an orderly execution code into two completely opposite execution sequence code processes, thus realizing AOP ingeniously. This also becomes the AOP foundation of the Action layer of Struts 2.