本章讲述Struts2 的工作原理。
读者如果曾经学习过Struts1.x 或者有过Struts1.x 的开发经验,那么千万不要想当然地以为这一章可以跳过。实际上Struts1.x Struts2 并无我们想象的血缘关系。虽然Struts2 的开发小组极力保留Struts1.x 的习惯,但因为Struts2 的核心设计完全改变,从思想到设计到工作流程,都有了很大的不同。
Struts2 Struts 社区和WebWork 社区的共同成果,我们甚至可以说,Struts2 WebWork 的升级版,他采用的正是WebWork 的核心,所以,Struts2 并不是一个不成熟的产品,相反,构建在WebWork 基础之上的Struts2 是一个运行稳定、性能优异、设计成熟的WEB 框架。
本章主要对Struts 的源代码进行分析,因为Struts2 WebWork 的关系如此密不可分,因此,读者需要下载xwork 的源代码,访问 [url]http://www.opensymphony.com/xwork/download.action[/url] 即可自行下载。
下载的Struts2 源代码文件是一个名叫struts-2.1.0-src.zip 的压缩包,里面的目录和文件非常多,读者可以定位到struts-2.1.0-src"struts-2.0.10"src"core"src"main"java 目录下查看Struts2 的源文件,如图14 所示。
(图14

主要的包和类

Struts2 框架的正常运行,除了占核心地位的xwork 的支持以外,Struts2 本身也提供了许多类,这些类被分门别类组织到不同的包中。从源代码中发现,基本上每一个Struts2 类都访问了WebWork 提供的功能,从而也可以看出Struts2 WebWork 千丝万缕的联系。但无论如何,Struts2 的核心功能比如将请求委托给哪个Action 处理都是由xwork 完成的,Struts2 只是在WebWork 的基础上做了适当的简化、加强和封装,并少量保留Struts1.x 中的习惯。
以下是对各包的简要说明:
包名
说明
org.apache.struts2. components
该包封装视图组件,Struts2 在视图组件上有了很大加强,不仅增加了组件的属性个数,更新增了几个非常有用的组件,如updownselect doubleselect datetimepicker token tree 等。
另外,Struts2 可视化视图组件开始支持主题(theme) ,缺省情况下,使用自带的缺省主题,如果要自定义页面效果,需要将组件的theme 属性设置为simple
org.apache.struts2. config
该包定义与配置相关的接口和类。实际上,工程中的xml properties 文件的读取和解析都是由WebWork 完成的,Struts 只做了少量的工作。
org.apache.struts2.dispatcher
Struts2 的核心包,最重要的类都放在该包中。
org.apache.struts2.impl
该包只定义了3 个类,他们是StrutsActionProxy StrutsActionProxyFactory StrutsObjectFactory ,这三个类都是对xwork 的扩展。
org.apache.struts2.interceptor
定义内置的截拦器。
org.apache.struts2.util
实用包。
org.apache.struts2.validators
只定义了一个类:DWRValidator
org.apache.struts2.views
提供freemarker jsp velocity 等不同类型的页面呈现。
下表是对一些重要类的说明:
类名
说明
org.apache.struts2.dispatcher. Dispatcher
       该类有两个作用:
       1 、初始化
       2 、调用指定的Action execute() 方法。
org.apache.struts2.dispatcher. FilterDispatcher
       这是一个过滤器。文档中已明确说明,如果没有经验,配置时请将url-pattern 的值设成/*
       该类有四个作用:
       1 、执行Action
       2 、清理ActionContext ,避免内存泄漏
       3 、处理静态内容(Serving static content
       4 、为请求启动xwork’s 的截拦器链。
com.opensymphony.xwork2. ActionProxy
       Action 的代理接口。
com.opensymphony.xwork2. ctionProxyFactory
       生产ActionProxy 的工厂。
com.opensymphony.xwork2.ActionInvocation
       负责调用Action 和截拦器。
com.opensymphony.xwork2.config.providers. XmlConfigurationProvider
       负责Struts2 的配置文件的解析。

Struts2的工作机制

3.1Struts2体系结构图

       Strut2 的体系结构如图15 所示:
       (图15

3.2Struts2的工作机制

       从图15 可以看出,一个请求在Struts2 框架中的处理大概分为以下几个步骤:
1 、客户端初始化一个指向Servlet 容器(例如Tomcat )的请求;
2 、这个请求经过一系列的过滤器(Filter )(这些过滤器中有一个叫做ActionContextCleanUp 的可选过滤器,这个过滤器对于Struts2 和其他框架的集成很有帮助,例如:SiteMesh Plugin );
3 、接着FilterDispatcher 被调用,FilterDispatcher 询问ActionMapper 来决定这个请求是否需要调用某个Action
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
注:以上步骤参考至网上,具体网址已忘记。在此表示感谢!

3.3Struts2源代码分析

       Struts1.x 不同,Struts2 的启动是通过FilterDispatcher 过滤器实现的。下面是该过滤器在web.xml 文件中的配置:
代码清单6 web.xml (截取)
    < filter >
       < filter-name > struts2 </ filter-name >
       < filter-class >
           org.apache.struts2.dispatcher.FilterDispatcher
       </ filter-class >
    </ filter >
    < filter-mapping >
       < filter-name > struts2 </ filter-name >
       < url-pattern > /* </ url-pattern >
    </ filter-mapping >
       Struts2 建议,在对Struts2 的配置尚不熟悉的情况下,将url-pattern 配置为/* ,这样该过滤器将截拦所有请求。
       实际上, FilterDispatcher 除了实现 Filter 接口以外,还实现了 StrutsStatics 接口,继承代码如下:
代码清单 7 FilterDispatcher 结构
public class FilterDispatcher implements StrutsStatics, Filter {
}
StrutsStatics 并没有定义业务方法,只定义了若干个常量。 Struts2 对常用的接口进行了重新封装,比如 HttpServletRequest HttpServletResponse HttpServletContext 等。 以下是 StrutsStatics 的定义:
代码清单 8 StrutsStatics.java
public interface StrutsStatics {
    /**
     * Constant for the HTTP request object.
     */
    public static final String HTTP_REQUEST = "com.opensymphony.xwork2.dispatcher.HttpServletRequest" ;
    /**
     * Constant for the HTTP response object.
     */
    public static final String HTTP_RESPONSE = "com.opensymphony.xwork2.dispatcher.HttpServletResponse" ;
    /**
     * Constant for an HTTP request dispatcher} .
     */
    public static final String SERVLET_DISPATCHER = "com.opensymphony.xwork2.dispatcher.ServletDispatcher" ;
    /**
     * Constant for the servlet context} object.
     */
    public static final String SERVLET_CONTEXT = "com.opensymphony.xwork2.dispatcher.ServletContext" ;
    /**
     * Constant for the JSP page context} .
     */
public static final String PAGE_CONTEXT = "com.opensymphony.xwork2.dispatcher.PageContext" ;
    /** Constant for the PortletContext object */
    public static final String STRUTS_PORTLET_CONTEXT = "struts.portlet.context" ;
}
    容器启动后, FilterDispatcher 被实例化,调用 init(FilterConfig filterConfig) 方法。该方法创建 Dispatcher 类的对象,并且将 FilterDispatcher 配置的初始化参数传到对象中(详情请参考代码清单 10 ),并负责 Action 的执行。然后得到参数 packages ,值得注意的是,还有另外三个固定的包和该参数进行拼接,分别是 org.apache.struts2.static template 、和 org.apache.struts2.interceptor.debugging ,中间用空格隔开,经过解析将包名变成路径后存储到一个名叫 pathPrefixes 的数组中,这些目录中的文件会被自动搜寻。
代码清单 9 FilterDispatcher.init() 方法
    public void init(FilterConfig filterConfig) throws ServletException {
        this .filterConfig = filterConfig;      
        dispatcher = createDispatcher(filterConfig);
        dispatcher.init();      
        String param = filterConfig.getInitParameter( "packages" );
        String packages = "org.apache.struts2.static template org.apache.struts2.interceptor.debugging" ;
        if (param != null ) {
            packages = param + " " + packages;
        }
        this . pathPrefixes = parse(packages);
}
代码清单 10 FilterDispatcher.createDispatcher() 方法
    protected Dispatcher createDispatcher(FilterConfig filterConfig) {
        Map<String,String> params = new HashMap<String,String>();
        for (Enumeration e = filterConfig.getInitParameterNames(); e.hasMoreElements(); ) {
            String name = (String) e.nextElement();
            String value = filterConfig.getInitParameter(name);
            params.put(name, value);
        }
        return new Dispatcher(filterConfig.getServletContext(), params);
   }
    当用户向 Struts2 发送请求时, FilterDispatcher doFilter() 方法自动调用,这个方法非常关键。首先, Struts2 对请求对象进行重新包装,此次包装根据请求内容的类型不同,返回不同的对象,如果为 multipart/form-data 类型,则返回 MultiPartRequestWrapper 类型的对象,该对象服务于文件上传,否则返回 StrutsRequestWrapper 类型的对象, MultiPartRequestWrapper StrutsRequestWrapper 的子类,而这两个类都是 HttpServletRequest 接口的实现。包装请求对象如代码清单 11 所示:
代码清单 11 FilterDispatcher. prepareDispatcherAndWrapRequest() 方法
protected HttpServletRequest prepareDispatcherAndWrapRequest(
        HttpServletRequest request,
        HttpServletResponse response) throws ServletException {
        Dispatcher du = Dispatcher.getInstance();
        if (du == null ) {
            Dispatcher.setInstance(dispatcher);         
            dispatcher.prepare(request, response);
        } else {
            dispatcher = du;
        }       
         try {
            request = dispatcher.wrapRequest(request, getServletContext());
        } catch (IOException e) {
            String message = "Could not wrap servlet request with MultipartRequestWrapper!" ;
            LOG.error(message, e);
             throw new ServletException(message, e);
        }
        return request;
}
    request 对象重新包装后,通过 ActionMapper getMapping() 方法得到请求的 Action Action 的配置信息存储在 ActionMapping 对象中,该语句如下: mapping = actionMapper.getMapping(request, dispatcher.getConfigurationManager()); 。下面是 ActionMapping 接口的实现类 DefaultActionMapper getMapping() 方法的源代码:
代码清单 12 DefaultActionMapper.getMapping() 方法
    public ActionMapping getMapping(HttpServletRequest request,
            ConfigurationManager configManager) {
        ActionMapping mapping = new ActionMapping();
        String uri = getUri(request); // 得到请求路径的 URI ,如: testAtcion.action testAction!method
        uri = dropExtension(uri); // 删除扩展名,默认扩展名为 action ,在代码中的定义是 List extensions = new ArrayList() {{ add("action");}};
        if (uri == null ) {
             return null ;
        }
        parseNameAndNamespace(uri, mapping, configManager); // uri 变量中解析出 Action name namespace
        handleSpecialParameters(request, mapping); // 将请求参数中的重复项去掉
       // 如果 Action name 没有解析出来,直接返回
        if (mapping.getName() == null ) {
            return null ;
        }
      
       // 下面处理形如 testAction!method 格式的请求路径
        if ( allowDynamicMethodCalls ) {
            // handle "name!method" convention.
            String name = mapping.getName();
            int exclamation = name.lastIndexOf( "!" );// ! Action 名称和方法名的分隔符
            if (exclamation != -1) {
                mapping.setName(name.substring(0, exclamation)); // 提取左边为 name
                mapping.setMethod(name.substring(exclamation + 1)); // 提取右边的 method
            }
        }
        return mapping;
    }
    该代码的活动图如下:
   
    (图 16
从代码中看出, getMapping() 方法返回 ActionMapping 类型的对象,该对象包含三个参数: Action name namespace 和要调用的方法 method
   
    如果 getMapping() 方法返回 ActionMapping 对象为 null ,则 FilterDispatcher 认为用户请求不是 Action ,自然另当别论, FilterDispatcher 会做一件非常有意思的事:如果请求以 /struts 开头,会自动查找在 web.xml 文件中配置的 packages 初始化参数,就像下面这样 ( 注意粗斜体部分 )
代码清单 13 web.xml( 部分 )
    < filter >
       < filter-name > struts2 </ filter-name >
       < filter-class >
           org.apache.struts2.dispatcher.FilterDispatcher
       </ filter-class >
       < init-param >
           < param-name > packages </ param-name >
           < param-value > com.lizanhong.action </ param-value >
       </ init-param >
    </ filter >
    FilterDispatcher 会将 com.lizanhong.action 包下的文件当作静态资源处理,即直接在页面上显示文件内容,不过会忽略扩展名为 class 的文件。比如在 com.lizanhong.action 包下有一个 aaa.txt 的文本文件,其内容为“×××”,访问 [url]http://localhost:8081/Struts2Demo/struts/aaa.txt[/url] 时会有如图 17 的输出:
(图 17
查找静态资源的源代码如清单 14
代码清单 14 FilterDispatcher.findStaticResource() 方法
    protected void findStaticResource(String name, HttpServletRequest request, HttpServletResponse response) throws IOException {
        if (!name.endsWith( ".class" )) { // 忽略 class 文件
           // 遍历 packages 参数
            for (String pathPrefix : pathPrefixes ) {
                InputStream is = findInputStream(name, pathPrefix); // 读取请求文件流
                if (is != null ) {
                    ……(省略部分代码)
                    // set the content-type header
                    String contentType = getContentType(name); // 读取内容类型
                    if (contentType != null ) {
                        response.setContentType(contentType); // 重新设置内容类型
                    }
                  ……(省略部分代码)
                    try {
                     // 将读取到的文件流以每次复制 4096 个字节的方式循环输出
                        copy(is, response.getOutputStream());
                    } finally {
                        is.close();
                    }
                    return ;
                }
            }
        }
    }
    如果用户请求的资源不是以 /struts 开头——可能是 .jsp 文件,也可能是 .html 文件,则通过过滤器链继续往下传送,直到到达请求的资源为止。
    如果 getMapping() 方法返回有效的 ActionMapping 对象,则被认为正在请求某个 Action ,将调用 Dispatcher.serviceAction(request, response, servletContext, mapping) 方法,该方法是处理 Action 的关键所在。上述过程的源代码如清单 15 所示。
代码清单 15 FilterDispatche r.doFilter() 方法
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        ServletContext servletContext = getServletContext();
        String timerKey = "FilterDispatcher_doFilter: " ;
        try {
            UtilTimerStack.push(timerKey);
            request = prepareDispatcherAndWrapRequest(request, response); // 重新包装 request
            ActionMapping mapping;
            try {
                mapping = actionMapper.getMapping(request, dispatcher.getConfigurationManager()); // 得到存储 Action 信息的 ActionMapping 对象
            } catch (Exception ex) {
               ……(省略部分代码)
                return ;
            }
            if (mapping == null ) { // 如果 mapping null ,则认为不是请求 Action 资源
                 String resourcePath = RequestUtils.getServletPath(request);
                if ( "" .equals(resourcePath) && null != request.getPathInfo()) {
                    resourcePath = request.getPathInfo();
                }
              // 如果请求的资源以 /struts 开头,则当作静态资源处理
                if ( serveStatic && resourcePath.startsWith( "/struts" )) {
                    String name = resourcePath.substring( "/struts" .length());
                    findStaticResource(name, request, response);
                } else {
                    // 否则,过滤器链继续往下传递
                    chain.doFilter(request, response);
                }
                // The framework did its job here
                return ;
            }
           // 如果请求的资源是 Action ,则调用 serviceAction 方法。
            dispatcher.serviceAction(request, response, servletContext, mapping);
        } finally {
            try {
                ActionContextCleanUp.cleanUp(req);
            } finally {
                UtilTimerStack.pop(timerKey);
            }
        }
    }
   
    这段代码的活动图如图 18 所示:
(图 18
    Dispatcher.serviceAction() 方法中,先加载 Struts2 的配置文件,如果没有人为配置,则默认加载 struts-default.xml struts-plugin.xml struts.xml ,并且将配置信息保存在形如 com.opensymphony.xwork2.config.entities.XxxxConfig 的类中。
    com.opensymphony.xwork2.config.providers.XmlConfigurationProvider 负责配置文件的读取和解析, addAction() 方法负责读取 <action> 标签,并将数据保存在 ActionConfig 中; addResultTypes() 方法负责将 <result-type> 标签转化为 ResultTypeConfig 对象; loadInterceptors() 方法负责将 <interceptor> 标签转化为 InterceptorConfi 对象; loadInterceptorStack() 方法负责将 <interceptor-ref> 标签转化为 InterceptorStackConfig 对象; loadInterceptorStacks() 方法负责将 <interceptor-stack> 标签转化成 InterceptorStackConfig 对象。而上面的方法最终会被 addPackage() 方法调用,将所读取到的数据汇集到 PackageConfig 对象中,细节请参考代码清单 16
代码清单 16 XmlConfigurationProvider.addPackage() 方法
    protected PackageConfig addPackage(Element packageElement) throws ConfigurationException {
        PackageConfig newPackage = buildPackageContext(packageElement);
        if (newPackage.isNeedsRefresh()) {
            return newPackage;
        }
        if (LOG.isDebugEnabled()) {
            LOG.debug( "Loaded " + newPackage);
        }
        // add result types (and default result) to this package
        addResultTypes(newPackage, packageElement);
        // load the interceptors and interceptor stacks for this package
        loadInterceptors(newPackage, packageElement);
        // load the default interceptor reference for this package
        loadDefaultInterceptorRef(newPackage, packageElement);
        // load the default class ref for this package
        loadDefaultCla***ef(newPackage, packageElement);
        // load the global result list for this package
        loadGlobalResults(newPackage, packageElement);
        // load the global exception handler list for this package
        loadGlobalExceptionMappings(newPackage, packageElement);
        // get actions
        NodeList actionList = packageElement.getElementsByTagName( "action" );
        for ( int i = 0; i < actionList.getLength(); i++) {
            Element actionElement = (Element) actionList.item(i);
            addAction(actionElement, newPackage);
        }
        // load the default action reference for this package
        loadDefaultActionRef(newPackage, packageElement);
        configuration.addPackageConfig(newPackage.getName(), newPackage);
        return newPackage;
    }
   
    活动图如图 19 所示:
(图 19
    配置信息加载完成后,创建一个 Action 的代理对象—— ActionProxy 引用,实际上对 Action 的调用正是通过 ActionProxy 实现的,而 ActionProxy 又由 ActionProxyFactory 创建, ActionProxyFactory 是创建 ActionProxy 的工厂。
注: ActionProxy ActionProxyFactory 都是接口,他们的默认实现类分别是 DefaultActionProxy DefaultActionProxyFactory ,位于 com.opensymphony.xwork2 包下。
    在这里,我们绝对有必要介绍一下 com.opensymphony.xwork2.DefaultActionInvocation 类,该类是对 ActionInvocation 接口的默认实现,负责 Action 和截拦器的执行。
    DefaultActionInvocation 类中,定义了 invoke() 方法,该方法实现了截拦器的递归调用和执行 Action execute() 方法。其中,递归调用截拦器的代码如清单 17 所示:
代码清单 17 :调用截拦器, DefaultActionInvocation.invoke() 方法的部分代码
       if ( interceptors .hasNext()) {
              // 从截拦器集合中取出当前的截拦器
               final InterceptorMapping interceptor = (InterceptorMapping) interceptors .next();
               UtilTimerStack.profile( "interceptor: " +interceptor.getName(),
                      new UtilTimerStack.ProfilingBlock<String>() {
                         public String doProfiling() throws Exception {
                            // 执行截拦器( Interceptor )接口中定义的 intercept 方法
                             resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation. this );
                            return null ;
                         }
               });
           }
    从代码中似乎看不到截拦器的递归调用,其实是否递归完全取决于程序员对程序的控制,先来看一下 Interceptor 接口的定义:
代码清单 18 Interceptor.java
public interface Interceptor extends Serializable {
    void destroy();
    void init();
    String intercept(ActionInvocation invocation) throws Exception;
}
    所有的截拦器必须实现 intercept 方法,而该方法的参数恰恰又是 ActionInvocation ,所以,如果在 intercept 方法中调用 invocation.invoke() ,代码清单 17 会再次执行,从 Action Intercepor 列表中找到下一个截拦器,依此递归。下面是一个自定义截拦器示例:
代码清单 19 CustomIntercepter.java
public class CustomIntercepter extends AbstractInterceptor {
    @Override
    public String intercept(ActionInvocation actionInvocation) throws Exception
    {
       actionInvocation.invoke();
       return " 李赞红 " ;
    }
}
    截拦器的调用活动图如图 20 所示:
(图 20
    如果截拦器全部执行完毕,则调用 invokeActionOnly() 方法执行 Action invokeActionOnly() 方法基本没做什么工作,只调用了 invokeAction() 方法。
    为了执行 Action ,必须先创建该对象,该工作在 DefaultActionInvocation 的构造方法中调用 init() 方法早早完成。调用过程是: DefaultActionInvocation()->init()->createAction() 。创建 Action 的代码如下:
代码清单 20 DefaultActionInvocation.createAction() 方法
    protected void createAction(Map contextMap) {
        try {
            action = objectFactory.buildAction(proxy.getActionName(), proxy.getNamespace(), proxy.getConfig(), contextMap);
        } catch (InstantiationException e) {
       ……异常代码省略
        }
    }
    Action 创建好后,轮到 invokeAction() 大显身手了,该方法比较长,但关键语句实在很少,用心点看不会很难。
代码清单 20 DefaultActionInvocation.invokeAction() 方法
protected String invokeAction(Object action, ActionConfig actionConfig) throws Exception {
    // 获取 Action 中定义的 execute() 方法名称 , 实际上该方法是可以随便定义的
        String methodName = proxy.getMethod();
        String timerKey = "invokeAction: " +proxy.getActionName();
        try {
            UtilTimerStack.push(timerKey);           
            Method method;
            try {
              // 将方法名转化成 Method 对象
                method = getAction().getClass().getMethod(methodName, new Class[0]);
            } catch (NoSuchMethodException e) {
                // hmm -- OK, try doXxx instead
                try {
                  // 如果 Method 出错 , 则尝试在方法名前加 do, 再转成 Method 对象
                    String altMethodName = "do" + methodName.substring(0, 1).toUpperCase() + methodName.substring(1);
                    method = getAction().getClass().getMethod(altMethodName, new Class[0]);
                } catch (NoSuchMethodException e1) {
                    // throw the original one
                    throw e;
                }
            }
           // 执行方法
            Object methodResult = method.invoke(action, new Object[0]);
            // 处理跳转
        if (methodResult instanceof Result) {
                this .result = (Result) methodResult;
                return null ;
            } else {
                return (String) methodResult;
            }
        } catch (NoSuchMethodException e) {
              ……省略异常代码
        } finally {
            UtilTimerStack.pop(timerKey);
        }
    }
    刚才使用了一段插述,我们继续回到 ActionProxy 类。
    我们说 Action 的调用是通过 ActionProxy 实现的,其实就是调用了 ActionProxy.execute() 方法,而该方法又调用了 ActionInvocation.invoke() 方法。归根到底,最后调用的是 DefaultActionInvocation.invokeAction() 方法。
    以下是调用关系图:
   
    其中:
Ø         ActionProxy :管理 Action 的生命周期,它是设置和执行 Action 的起始点。
Ø         ActionInvocation :在 ActionProxy 层之下,它表示了 Action 的执行状态。它持有 Action 实例和所有的 Interceptor
    以下是 serviceAction() 方法的定义:
代码清单 21 Dispatcher.serviceAction() 方法
        public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context,
                              ActionMapping mapping) throws ServletException {
        Map<String, Object> extraContext = createContextMap(request, response, mapping, context);
        // 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);
        if (stack != null ) {
            extraContext.put(ActionContext.VALUE_STACK, ValueStackFactory.getFactory().createValueStack(stack));
        }
        String timerKey = "Handling request from Dispatcher" ;
        try {
            UtilTimerStack.push(timerKey);
            String namespace = mapping.getNamespace();
            String name = mapping.getName();
            String method = mapping.getMethod();
            Configuration config = configurationManager.getConfiguration();
            ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory. class ).createActionProxy(
                    namespace, name, extraContext, true , false );
            proxy.setMethod(method);
            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 (stack != null ) {
                request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
            }
        } catch (ConfigurationException e) {
            LOG.error( "Could not find action or result" , e);
            sendError(request, response, context, HttpServletResponse.SC_NOT_FOUND, e);
        } catch (Exception e) {
            throw new ServletException(e);
        } finally {
            UtilTimerStack.pop(timerKey);
        }
    }
    最后,通过 Result 完成页面的跳转。

3.4 本小节总结

       总体来讲,Struts2 的工作机制比Struts1.x 要复杂很多,但我们不得不佩服Struts WebWork 开发小组的功底,代码如此优雅,甚至能够感受看到两个开发小组心神相通的默契。两个字:佩服。
       以下是Struts2 运行时调用方法的顺序图:
(图21

四、      总结

阅读源代码是一件非常辛苦的事,对读者本身的要求也很高,一方面要有扎实的功底,另一方面要有超强的耐力和恒心。本章目的就是希望能帮助读者理清一条思路,在必要的地方作出简单的解释,达到事半功倍的效果。
当然,笔者不可能为读者解释所有类,这也不是我的初衷。Struts2+xwork 一共有700 余类,除了为读者做到现在的这些,已无法再做更多的事情。读者可以到Struts 官方网站下载帮助文档,慢慢阅读和理解,相信会受益颇丰。
本章并不适合java 语言初学者或者对java 博大精深的思想理解不深的读者阅读,这其中涉及到太多的术语和类的使用,特别不要去钻牛角尖,容易使自信心受损。基本搞清楚Struts2 的使用之后,再回过头来阅读本章,对一些知识点和思想也许会有更深的体会。
如果读者的java 功底比较浑厚,而且对Struts2 充满兴趣,但又没太多时间研究,不妨仔细阅读本章,再对照Struts 的源代码,希望对您有所帮助。