web development line

Struts2 is roughly divided into two parts: one is the initialization of the struts2 system, and the other is that struts2 processes the request and responds to the request.

Let's talk about my personal understanding of struts2's request processing process:

The following is the main code in the doFilter method of the StrutsPrepareAndExecuteFilter filter:

prepare.setEncodingAndLocale(request, response);
prepare.createActionContext(request, response);
prepare.assignDispatcherToThread() ;
if ( excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
chain.doFilter(request, response);
} else {
request = prepare.wrapRequest(request);
ActionMapping mapping = prepare.findActionMapping(request, response, true);
if (mapping == null) {
boolean handled = execute.executeStaticResourceRequest(request, response);
if (!handled) {
chain.doFilter(request, response);
}
} else {
execute.executeAction(request, response, mapping);
}
}
When the system is initialized, the init method of StrutsPrepareAndExecuteFilter is executed, and two objects, PrepareOperations and ExecuteOperations, are instantiated. An object is the encapsulation of some preparation operations made before the actual response to the request, and ExecuteOperations is the encapsulation of the response request, but in fact, these two objects ultimately call the methods of the core dispatcher Dispatcher object.

prepare.setEncodingAndLocale(request, response); This sentence deals with request encoding and response Locale, which internally calls the prepare method of Dipatcher. The following is the source code:

/**
     * Sets the request encoding and locale on the response
     */
    public void setEncodingAndLocale (HttpServletRequest request, HttpServletResponse response) {
        dispatcher.prepare(request, response);
    }
The logic is that if i18n encoding is written in the struts2 configuration file, the encoding in the configuration file is used, otherwise the request.setCharacterEncoding() method will not be called. As for the value of the response Locale, the principle of encoding is the same, but the specific logic source code is a bit more, but it is not difficult, so I will not explain it here, everyone should understand it.

prepare.createActionContext(request, response);

ActionContext oldContext = ActionContext.getContext();
        if (oldContext != null) {
            // detected existing context, so we are probably in a forward
    //because projects are generally not distributed Application, it will not be executed here
            ctx = new ActionContext(new HashMap<String, Object>(oldContext.getContextMap()));
        } else {
    //This is the code that will be executed
            ValueStack stack = dispatcher.getContainer().getInstance (ValueStackFactory.class).createValueStack();
            stack.getContext().putAll(dispatcher.createContextMap(request, response, null, servletContext));
            ctx = new ActionContext(stack.getContext());
        }
        request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);
        ActionContext.setContext(ctx) ;
        return ctx;
ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack(); Get ValueStackFactory from struts2 container and create ValueStack

stack.getContext().putAll(dispatcher.createContextMap(request, response, null, servletContext)); Copy the data in ActionContext to ValueStack, so we can get all the data we want from ActionContext and ValueStack.

dispatcher.createContextMap

// request map wrapping the http request objects
        Map requestMap = new RequestMap(request);

        // parameters map wrapping the http parameters.  ActionMapping parameters are now handled and applied separately
        Map params = new HashMap(request.getParameterMap());

        // session map wrapping the http session
        Map session = new SessionMap(request);

        // application map wrapping the ServletContext
        Map application = new ApplicationMap(context);

        Map<String,Object> extraContext = createContextMap(requestMap, params, session, application, request, response, context);

        if (mapping != null) {
            extraContext.put(ServletActionContext.ACTION_MAPPING, mapping);
        }
        return extraContext; In
this method, the original Request, Parameters, Session, and ServletContext of the Servlet are converted into Map, and then the converted Map and the original Servlet object are passed into another overloaded createContextMap method of Dispatcher. The following is its Source code:

public HashMap<String,Object> createContextMap(Map requestMap,
                                    Map parameterMap,
                                    Map sessionMap,
                                    Map applicationMap,
                                    HttpServletRequest request,
                                    HttpServletResponse response,
                                    ServletContext servletContext) {
        HashMap<String,Object> extraContext = new HashMap<String,Object>() ;
        extraContext.put(ActionContext.PARAMETERS, new HashMap(parameterMap));
        extraContext.put(ActionContext.SESSION, sessionMap);
        extraContext.put(ActionContext.APPLICATION, applicationMap);

        Locale locale;
        if (defaultLocale != null) {
            locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());
        } else {
            locale = request.getLocale();
        }

        extraContext.put(ActionContext.LOCALE, locale);
        //extraContext.put(ActionContext.DEV_MODE, Boolean.valueOf(devMode));

        extraContext.put(StrutsStatics.HTTP_REQUEST, request);


















Now go back to

ActionContext.setContext(ctx) in the prepare.createActionContext method; put the created ActionContext object into ThreadLocal<T> and bind it to the current thread to easily obtain the ActionContext object elsewhere.

Add to the core filter, prepare.assignDispatcherToThread(); From the name of the method, we know that the Dispatcher object is bound to the current thread, that is, it is put into a ThreadLocal<T> object. The purpose of this is to Solve the problem of multi-threaded concurrent access, because only one Dispathcer object is created, and the creation code is in the init method of StrutsPrepareAndExecuteFilter, and the init method will only be executed once. Of course, there is only one Dispatcher object, and Web applications are born with multiple Thread environment, so putting Dispatcher in ThreadLocal<T> becomes the best choice. This is different from the reason why the above ActionContext object is put into ThreadLocal<T>, because every time a request arrives, the system will create an ActionContext object for it. This ActionContext is exclusive to the request, and there is no multi-threading problem. , so the object is put into ThreadLocal<T> for convenience.

if ( excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
chain.doFilter(request, response);
} This judgment is because struts2 supports URL pattern matching that is not included. Although a request enters the struts2 filter, if the URL of the request is not included, sturts2 simply calls the chain.doFilter method to release it. In other cases, it will enter the else part and call Action to process the request.

request = prepare.wrapRequest(request); To wrap HttpServletRequest, refer to its source code to know that if it is a file upload, wrap the HttpServletRequest into a MultiPartRequestWrapper object, which specializes in processing file upload requests. If the file is not uploaded, HttpServletRequest is packaged into StrutsRequestWrapper, the StrutsRequestWrapper class overrides the getAttribute method of its parent class, and the behavior of this method is modified a little. The request object finds the specified attribute, and the getAttribute method of StrutsRequestWrapper is to search in the request object first. If it is not found, it will go to the ValueStack to find it. The following source code is the proof:

// If not found, then try the ValueStack
                        ctx. put("__requestWrapper.getAttribute", Boolean.TRUE);
                        ValueStack stack = ctx.getValueStack();
                        if (stack != null) {
                            attribute = stack.findValue(s);
                        }
This is why the attribute of the value in the ValueStack can also be accessed by using EL expressions in the JSP page.

ActionMapping mapping = prepare.findActionMapping(request, response, true); This sentence has nothing to say, just get the Action mapping information.

If the requested static page will execute execute.executeStaticResourceRequest(request, response); inside the method, it will judge whether it has the ability to process the request, and if so, call chain.doFilter to release it. The implementation is basically the same, and it is returned as it is to the client.

What will actually execute the Action is this sentence: execute.executeAction(request, response, mapping); This method calls the serviceAction method of the Dispatcher. Let's enter the method and see, the following is the important code in the method:

    Configuration config = configurationManager .getConfiguration();
            ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
                    namespace, name, method, extraContext, true, false);

            request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());

            // if the ActionMapping says to go straight to a result, do it!
            if (mapping.getResult() != null) {
                Result result = mapping.getResult();
                result.execute(proxy.getInvocation());
            } else {
                proxy.execute();
            }

            // If there was a previous value stack then set it back onto the request
            if (!nullStack) {
                request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
            }
First get the Configuration object, get the container object through Configuration, then get the ActionProxyFactory, ActionProxy factory class from the container, and then create the ActionProxy, everyone knows that struts2 internally wraps the webwork framework, and ActionProxy is the dividing line between sturts and webwork, ActionProxy It is the portal for webwork framework.

By default, struts2 uses DefaultActionProxyFactory to create ActionProxy objects. The following is its createActionFactor method:

public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map<String, Object> extraContext, boolean executeResult, boolean cleanupContext) {

        ActionInvocation inv = new DefaultActionInvocation (extraContext, true);
        container.inject(inv);
        return createActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);
    }
public ActionProxy createActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) {

        DefaultActionProxy proxy = new DefaultActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);
        container.inject(proxy);
        proxy.prepare();
        return proxy;
    }
These are two overloaded methods. The first method calls the second method. In the first method, the ActionInvocation object is created, and the object it depends on is injected using the container, and then the ActionProxy object is created. It also injects its dependent objects, such as ObjectFctory, Configuration objects, etc. Then use the proxy.prepare() method, which has a resolveMethod(); method. This method is very simple, that is, if the method attribute is not specified when configuring the Action, the method attribute value will be assigned to execute, which is why execute is the reason for the default execution method of Action. There is also a very important method called invocation.init(this); that is, the init method of ActionInvocation is called, and ActionProxy is passed on by itself. Of course, ActionProxy will be cached in ActionInvocation.

The default implementation of ActionInvocation in struts2 is the DefaultActionInvocation class, enter the init method of this class, and the following is the important code in the method:

createAction(contextMap);

        if (pushAction) {
            stack.push(action);
            contextMap.put("action ", action);
        }

        invocationContext = new ActionContext(contextMap);
        invocationContext.setName(proxy.getActionName());

        // get a new List so we don't get problems with the iterator if someone changes the list
        List<InterceptorMapping> interceptorList = new ArrayList<InterceptorMapping>(proxy.getConfig().getInterceptors());
        interceptors = interceptorList.iterator( );
The first sentence is to create an Action. If you know something about the object creation of struts2, you will know that the creation of Action, Result, and Intercepter is created by the buildBean method of ObjectFactory, which internally calls Class.newInstance() ; created, so Action must have a no-argument constructor.

Pay attention to this sentence: stack.push(action); Here, the created Action is pushed into the ValuesStack, which is why the default Action is on the top of the stack. The following is to get all the interceptors configured by the Action and cache them in the ActionInvocation. The same is true for the Action, because the execution scheduling of the Action and the Interceptor is implemented by the ActionInvocation.

Now go back to the serviceAction method of Dispatcher. The next code after creating the ActionProxy object puts the ValueStack object into the request, which means that we can also obtain it through the HttpServletRequest object, as long as we know the key.

proxy.execute(); This sentence executes the execute method of ActionProxy. The default implementation of ActionProxy in struts2 is StrutsActionProxy. The following is the execute method of this class. The source code is as follows:

public String execute() throws Exception {
        ActionContext previous = ActionContext.getContext() ;
        ActionContext.setContext(invocation.getInvocationContext());
        try {
// This is for the new API:
// return RequestContextImpl.callInContext(invocation, new Callable<String>() {
// public String call() throws Exception {
// return invocation.invoke();
// }
// });

            return invocation.invoke();
        } finally {
            if (cleanupContext)
                ActionContext.setContext(previous);
        }
    }
The code is very simple, it is to call the invoke method of ActionInvocation to execute the interceptor and Action. The following is the source code of the invoke method. Because there are many attached codes in this method, only the important codes are picked up here:

if (interceptors.hasNext()) {
                final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next();
                String interceptorMsg = "interceptor: " + interceptor.getName();
                UtilTimerStack.push(interceptorMsg);
                try {
                                resultCode = interceptor.getInterceptor ().intercept(DefaultActionInvocation.this);
                            }
                finally {
                    UtilTimerStack.pop(interceptorMsg);
                }
            } else {
                resultCode = invokeActionOnly();
            }
//Some code omitted here...
// now execute the result, if we're supposed to
                if (proxy.getExecuteResult()) {
                    executeResult();
                }
Show here It is to execute all interceptors in the interceptor stack. When the interceptor is executed, the corresponding method in the Action will be executed according to the configuration. After the execution, a resultCode string will be obtained, and the system will search for the corresponding Result according to this string. .

The compact method is to execute the executeResult method, in which the corresponding Result object is created according to the Result configuration, and then the execute method of the Result is executed. For example, the most used ServletDispatcherResult, the execute method is mainly to call the dispatcher.forward(request, response) method , returns a page to Tomcat for parsing, and then renders the parsed content to the client browser.

So far, the entire execution process of struts2 is basically finished. If there are any mistakes, please correct me.

The following upload is a sequence diagram drawn by the individual for the execution process of struts2. If you are interested, you can take a look. Because the picture is too large, you need to zoom in to see it clearly. I hope it helps:

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326354273&siteId=291194637