Struts2原理:Action请求整个过程:对源码的跟踪

Struts2原理:Action请求整个过程:对源码的跟踪

这张工作原理图是官方提供的:

一个请求在Struts2框架中的处理大概分为以下几个步骤

1、客户端初始化一个指向Servlet容器(例如Tomcat)的请求

2、这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做ActionContextCleanUp的可选过滤器,这个过滤器对于Struts2和其他框架的集成很有帮助,例如:SiteMesh Plugin)

3、接着StrutsPrepareAndExecuteFilter(在struts2.1中就不用FilterDispatcher)被调用,StrutsPrepareAndExecuteFilter询问ActionMapper来决定这个请是否需要调用某个Action

4、如果ActionMapper决定需要调用某个Action,StrutsPrepareAndExecuteFilter把请求的处理交给ActionProxy

5、ActionProxy通过ConfigurationManager询问框架的配置文件,找到需要调用的Action类 ,这里,我们一般是从struts.xml配置中读取。

6、ActionProxy创建一个ActionInvocation的实例。

7、ActionInvocation实例使用命名模式来调用,在调用Action的过程前后,涉及到相关拦截器(Intercepter)的调用。

在调用拦截器的时候是采取回掉模式;

StrutsPrepareAndExecuteFilter是控制器的核心,就是mvc中c控制层的核心。下面粗略的分析下我理解的StrutsPrepareAndExecuteFilter工作流程和原理:StrutsPrepareAndExecuteFilter进行初始化并启用核心doFilter

 

最近工作任务不是很紧,时间也不能白白浪费,以前常用的struts2框架源码没去了解过,所以就跟踪一下struts2的整个执行过程.由于本人也是抱着学习的态度来阅读掩码,若文章在表述和代码方面如有不妥之处,欢迎批评指正。留下你的脚印,欢迎评论!希望能互相学习。

我这里的struts2源码是maven导jar包来查看源码的,这样方便多了,可以在IDE下查看源码。pom.xml文件如下

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>Struts_test_lishun</groupId>
  <artifactId>Struts_test_lishun</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>
   <dependencies>
    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.2</version>
    </dependency>
    <dependency>
        <groupId>org.apache.struts</groupId>
        <artifactId>struts2-core</artifactId>
        <version>2.3.24</version>
    </dependency>
  </dependencies>
</project>

1.实现从web.xml说起:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
     <!-- 添加一个过滤器-->
    <filter>
        <filter-name>action2</filter-name>
        <!-- struts2处理过程的入口 -->
        <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
        <init-param>
            <param-name >struts.serve.static.browserCache</param-name>
            <param-value>true</param-value>
        </init-param>
        <!-- 初始化参数(访问后缀为.do),这里的param-name都可以从后面提到的源码中查看到,具体的其他功能本人也不是全部了解,
                            有兴趣的可以自己再去深入,-->
         <init-param>
            <param-name>struts.action.extension</param-name>
            <param-value>do</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>action2</filter-name>
        <!-- 过滤所有的请求 -->
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

2:开始查看StrutsPrepareAndExecuteFilter 过滤器

2-1:初始化:(tomcat一启动就会执行到这里)

public void init(FilterConfig filterConfig) throws ServletException {
        InitOperations init = new InitOperations();
        Dispatcher dispatcher = null;
        try {
       //封装filterConfig,便于对xml文件的操作
            FilterHostConfig config = new FilterHostConfig(filterConfig);
       //初始化日志
            init.initLogging(config);
       //初始化dispatcher,struts2的核心类,大部分的操作都是在这里完成的
            dispatcher = init.initDispatcher(config);
            init.initStaticContentLoader(config, dispatcher);
          //初始化当前类属性:prepare 、execute(这两个属性会在doFilter里设置ActionContext和encoding的值)
            prepare = new PrepareOperations(dispatcher);
            execute = new ExecuteOperations(dispatcher);
            this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
       //这是是空函数。说是回调方法,这里不太懂,有了解可以给小弟指引下
            postInit(dispatcher, filterConfig);
        } finally {
            if (dispatcher != null) {
                dispatcher.cleanUpAfterInit();
            }
            init.cleanup();
        }
    }

2-2:对上面init.initDispatcher(config)的跟踪:

public Dispatcher initDispatcher( HostConfig filterConfig ) {
        //这里主要是对新建一个dispatcher,并加载配置文件中的初始化参数
        Dispatcher dispatcher = createDispatcher(filterConfig);
        //初始化dispatcher
        dispatcher.init();
        return dispatcher;
}

2-2-1:dispatcher.init()跟踪:

public void init() {

        if (configurationManager == null) {
            //把struts.xml文件进行封装(默认是名字是struts)
            configurationManager = createConfigurationManager(DefaultBeanSelectionProvider.DEFAULT_BEAN_NAME);
        }

        try {            init_FileManager();
            //加载org/apache/struts2/default.properties
            init_DefaultProperties(); // [1]
            //加载struts-default.xml,struts-plugin.xml,struts.xml
            init_TraditionalXmlConfigurations(); // [2]
            //下面的初始化代码。没去研究,到此已经把web.xml,和struts.xml文件给加载进来了
            init_LegacyStrutsProperties(); // [3]
            init_CustomConfigurationProviders(); // [5]
            init_FilterInitParameters() ; // [6]
            init_AliasStandardObjects() ; // [7]

            Container container = init_PreloadConfiguration();
            //注入dispatcher
            container.inject(this);
            init_CheckWebLogicWorkaround(container);

            if (!dispatcherListeners.isEmpty()) {
                for (DispatcherListener l : dispatcherListeners) {
                    l.dispatcherInitialized(this);
                }
            }
            errorHandler.init(servletContext);

        } catch (Exception ex) {
            if (LOG.isErrorEnabled())
                LOG.error("Dispatcher initialization failed", ex);
            throw new StrutsException(ex);
        }
    }

初始化就在此告一段落了。

———————-华丽的分割线————————-

3-1:访问某个action,StrutsPrepareAndExecuteFilter对所有的的请求都会过滤,此事就跟踪到;

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        try {
            if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
                chain.doFilter(request, response);
            } else {
                //设置类属性编码和国际化
                prepare.setEncodingAndLocale(request, response);
                //设置类属性action的上下文
                prepare.createActionContext(request, response);
                prepare.assignDispatcherToThread();
                //对request进行封装,继续跟踪下去代码会发现这里的作用就是根据不同的请求返回不同request的封装类(这里就是用到装饰者模式)
                request = prepare.wrapRequest(request);
         //返回ActionMapping:里面有通过struts.xml文件通过反射获取到action对应的类和方法
                ActionMapping mapping = prepare.findActionMapping(request, response, true);
         //若返回的ActionMapping为null,则表示不是调用action
                if (mapping == null) {
                    boolean handled = execute.executeStaticResourceRequest(request, response);
                    if (!handled) {
                        chain.doFilter(request, response);
                    }
                } else {

                    //这里进行了很多操作,比较重要就是页面参数值的注入,和执行action
                    execute.executeAction(request, response, mapping);
                }
            }
        } finally {
            prepare.cleanupRequest(request);
        }
    }

3-2:追踪代码-生成映射action的映射:

ActionMapping mapping = prepare.findActionMapping(request, response, true);
在PrepareOperations中,主要执行ActionMapper的getMapping(..)方法
public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response, boolean forceLookup) {
        ActionMapping mapping = (ActionMapping) request.getAttribute(STRUTS_ACTION_MAPPING_KEY);
        if (mapping == null || forceLookup) {
            try {
         //new一个ActionMapper实例并执行getMapping(..)方法
                mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager());
                if (mapping != null) {
                    request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping);
                }
            } catch (Exception ex) {
                dispatcher.sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);
            }
        }

        return mapping;
    }

 

3-2-1:追踪到ActionMapper的getMapping(..)方法;而ActionMapper是一个接口,方法由DefaultActionMapper类实现;

定位到DefaultActionMapper类的getMapping(..);

public ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager) {
        ActionMapping mapping = new ActionMapping();
        //获取到请求的url
        String uri = RequestUtils.getUri(request);
        //截取url,把含有";"的后面的字符截取掉
        int indexOfSemicolon = uri.indexOf(";");
        uri = (indexOfSemicolon > -1) ? uri.substring(0, indexOfSemicolon) : uri;
          
        //继续截取url,url的后缀截取掉(如.action和.do等),在这里就可以看见extensions变量,struts默认的后缀名就是.action
        uri = dropExtension(uri, mapping);
        if (uri == null) {
            return null;
        }
        //从url获取namespace和name并和mapping匹配
        parseNameAndNamespace(uri, mapping, configManager);
        handleSpecialParameters(request, mapping);
        return parseActionName(mapping);
    }

 

3-3:现在返回到doFilter方法,追踪到

execute.executeAction(request, response, mapping);方法;
 public void executeAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws ServletException {
        dispatcher.serviceAction(request, response, mapping);
    }

最终调用的是Dispatcher的serviceAction(…)方法

public void serviceAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping)
            throws ServletException {
        //封转上下文环境,主要将request、params、session等Map封装成为一个上下文Map、
        Map<String, Object> extraContext = createContextMap(request, response, mapping);

        // If there was a previous value stack, then create a new copy and pass it in to be used by the new Action
        ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);
        boolean nullStack = stack == null;
        if (nullStack) {
            ActionContext ctx = ActionContext.getContext();
            if (ctx != null) {
                stack = ctx.getValueStack();
            }
        }
        if (stack != null) {
            extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack));
        }

        String timerKey = "Handling request from Dispatcher";
        try {
            UtilTimerStack.push(timerKey);
            //从mapping(这里是已经封装好的mapping,struts.xml与请求url共同映射出来的数据)中获取命名空间
            String namespace = mapping.getNamespace();
        //从mapping中获取action的name
            String name = mapping.getName();
       //从mapping中获取请求方法(是获取动态的请求方法:在url后面加上 ‘!+方法名’)
            String method = mapping.getMethod();
       //生成action的代理类,执行页面参数值和根据反射执行请求方法
            ActionProxy proxy = getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
                    namespace, name, method, extraContext, true, false);

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

            //如果配置文件中执行的这个action配置了result,就直接转到result
            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);
            }
        } catch (ConfigurationException e) {
            logConfigurationException(request, e);
            sendError(request, response, HttpServletResponse.SC_NOT_FOUND, e);
        } catch (Exception e) {
            if (handleException || devMode) {
                sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
            } else {
                throw new ServletException(e);
            }
        } finally {
            UtilTimerStack.pop(timerKey);
        }
    }

3-3-1:追踪 ActionProxy proxy = getContainer().getInstance(ActionProxyFactory.class).createActionProxy( namespace, name, method, extraContext, true, false);

创建ActionProxy是由ActionProxyFactory实现类完成

DefaultActionProxyFactory createActionProxy方法

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);
        //StrutsActionProxyFactory 的createActionProxy方法,   StrutsActionProxyFactory是DefaultActionProxyFactory的子类,调用的是StrutsActionProxyFactory的方法
        return createActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);
    }

StrutsActionProxyFactory

public class StrutsActionProxyFactory extends DefaultActionProxyFactory {

    @Override
    public ActionProxy createActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) {
        
        StrutsActionProxy proxy = new StrutsActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);
        container.inject(proxy);
//继续跟踪代码: prepare()调用的是父类DefaultActionProxy的prepare()方法
        proxy.prepare();
        return proxy;
    }
}

DefaultActionProxy

protected void prepare() {
        String profileKey = "create DefaultActionProxy: ";
        try {
            UtilTimerStack.push(profileKey);
            config = configuration.getRuntimeConfiguration().getActionConfig(namespace, actionName);

            if (config == null && unknownHandlerManager.hasUnknownHandlers()) {
                config = unknownHandlerManager.handleUnknownAction(namespace, actionName);
            }
            if (config == null) {
                throw new ConfigurationException(getErrorMessage());
            }
              //获取执行method为空的方法名, 若为空则默认设置为"execute" 
            resolveMethod();

            if (!config.isAllowedMethod(method)) {
                throw new ConfigurationException("Invalid method: " + method + " for action " + actionName);
            }
              //创建action
            invocation.init(this);

        } finally {
            UtilTimerStack.pop(profileKey);
        }
    }

invocation.init(this)

public void init(ActionProxy proxy) {
        this.proxy = proxy;
        Map<String, Object> contextMap = createContextMap();

        // Setting this so that other classes, like object factories, can use the ActionProxy and other
        // contextual information to operate
        ActionContext actionContext = ActionContext.getContext();

        if (actionContext != null) {
            actionContext.setActionInvocation(this);
        }
         //这里开始创建action
        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();
    }

至此action就创建成功了,然后就是给action的属性赋值和执行action里的方法

3-4:重新回到Dispatcher的serviceAction(…)方法,当mapping.getResult() != null此时就跟踪到

proxy.execute();方法

ActionProxy是接口,execute()由StrutsActionProxy实现;

public String execute() throws Exception {
        ActionContext previous = ActionContext.getContext();
        ActionContext.setContext(invocation.getInvocationContext());
        try {
            //开始执行action和注入属性值,由DefaultActionInvocation实现这个方法
            return invocation.invoke();
        } finally {
            if (cleanupContext)
                ActionContext.setContext(previous);
        }
    }

DefaultActionInvocation

.....其他代码省略
try {
    //最主要执行了intercept方法,这里就是执行设置action的属性值和执行方法,由接口Interceptor的抽象类AbstractInterceptor的子类MethodFilterInterceptor执行intercept方法,
    //由于AbstractInterceptor的实现类很多,所以这段代码会执行很多次,至于为什么会执行多次,本人也还在研究,若有仁兄了解可以给小弟一点指引
         resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
    }
    finally {
      UtilTimerStack.pop(interceptorMsg);
    }
.....其他代码省略

MethodFilterInterceptor

@Override
    public String intercept(ActionInvocation invocation) throws Exception {
        if (applyInterceptor(invocation)) {
          //执行了ParametersInterceptor的doIntercept方法,
 return doIntercept(invocation);
        } 
        return invocation.invoke();
    }

ParametersInterceptor

@Override
    public String doIntercept(ActionInvocation invocation) throws Exception {
        Object action = invocation.getAction();
        if (!(action instanceof NoParameters)) {
            ActionContext ac = invocation.getInvocationContext();
            final Map<String, Object> parameters = retrieveParameters(ac);

            if (LOG.isDebugEnabled()) {
                LOG.debug("Setting params " + getParameterLogMap(parameters));
            }

            if (parameters != null) {
                Map<String, Object> contextMap = ac.getContextMap();
                try {
                    ReflectionContextState.setCreatingNullObjects(contextMap, true);
                    ReflectionContextState.setDenyMethodExecution(contextMap, true);
                    ReflectionContextState.setReportingConversionErrors(contextMap, true);

                    ValueStack stack = ac.getValueStack();
//给action的参数赋值
                    setParameters(action, stack, parameters);
                } finally {
                    ReflectionContextState.setCreatingNullObjects(contextMap, false);
                    ReflectionContextState.setDenyMethodExecution(contextMap, false);
                    ReflectionContextState.setReportingConversionErrors(contextMap, false);
                }
            }
        }
//最后执行action
        return invocation.invoke();
    }

至此,整个action的请求就结束了;

 

一个请求在Struts2框架中的处理大概分为以下几个步骤:

1 客户端发送请求;
2 这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做ActionContextCleanUp的可选过滤器,这个过滤器对于Struts2和其他框架的集成很有帮助,例如:SiteMesh Plugin)
3 接着FilterDispatcher被调用,FilterDispatcher询问ActionMapper来决定这个请是否需要调用某个Action。FilterDispatcher的功能如下:

       (1)执行Actions
       (2)清除ActionContext
       (3)维护静态内容

        (4)清除request生命周期内的XWork的interceptors

4 如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy
5 ActionProxy通过Configuration Manager询问框架的配置文件,找到需要调用的Action类
6 ActionProxy创建一个ActionInvocation的实例。
7 ActionInvocation实例使用命名模式来调用,在调用Action的过程前后,涉及到相关拦截器(Intercepter)的调用。
8 一旦Action执行完毕,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果。返回结果通常是(但不总是,也可 能是另外的一个Action链)一个需要被表示的JSP或者FreeMarker的模版。在表示的过程中可以使用Struts2 框架中继承的标签。在这个过程中需要涉及到ActionMapper

拦截器与过滤器:

1、拦截器是基于java反射机制的,而过滤器是基于函数回调的。2、过滤器依赖于servlet容器,而拦截器不依赖于servlet容器。
3、拦截器只能对Action请求起作用,而过滤器则可以对几乎所有请求起作用。
4、拦截器可以访问Action上下文、值栈里的对象,而过滤器不能。
5、在Action的生命周期中,拦截器可以多次调用,而过滤器只能在容器初始化时被调用一次。

在上述过程中所有的对象(Action,Results,Interceptors,等)都是通过ObjectFactory来创建的。

Struts2的目标很简单–使Web开发变得更加容易。为了达成这一目标,Struts2中提供了很多新特性,比如智能的默认设置、annotation的使用以及”惯例重于配置”原则的应用,而这一切都大大减少了XML配置。Struts2中的Action都是POJO,这一方面增强了Action本身的可测试性,另一方面也减小了框架内部的耦合度,而HTML表单中的输入项都被转换成了恰当的类型以供action使用。开发人员还可以通过拦截器(可以自定义拦截器或者使用Struts2提供的拦截器)来对请求进行预处理和后处理,这样一来,处理请求就变得更加模块化,从而进一步减小耦合度。模块化是一个通用的主题–可以通过插件机制来对框架进行扩展;开发人员可以使用自定义的实现来替换掉框架的关键类,从而获得框架本身所不具备的功能;可以用标签来渲染多种主题(包括自定义的主题);Action执行完毕以后,可以有多种结果类型–包括渲染JSP页面,Velocity和Freemarker模板,但并不仅限于这些。

转载请保留出处!:精品信息聚合网--只聚合精品信息 » Struts2原理:Action请求整个过程:对源码的跟踪