• 结合源码浅析Struts2与Spring整合的原理


    本文假设读者已经自己动手整合过Struts2和Spring这两个框架。我想说明的重点不是如何整合这两个框架,而是为什么经过配置之后Struts的Action等对象可以由Spring来管理,即找到两个框架的衔接点。

    笔者用的是框架版本分别为Struts-2.3和Spring-4.1。

    -------------------------------------------------------

    文章的结构如下:

    一、回顾Struts2与Spring整合的配置方法

    二、(重点)对关键配置的分析

    --------------------------------------------------------

    一、回顾Struts2与Spring整合的配置方法

    1. 创建一个普通的Web应用(含/WEB-INF/web.xml)

    2. 配置Struts2

      首先,我们要导入Struts2所需的Jar包(先不考虑整合用的包),导包的就不多说了。接下来,我们要创建struts.xml配置文件,我把配置文件放在CLASSPATH /src 中。这里我先创建一个测试用的package,配置文件的主要内容如下:

    1 <?xml version="1.0" encoding="UTF-8"?>
    2 <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN" "http://struts.apache.org/dtds/struts-2.3.dtd" >
    3 <struts>
    4     <package name="test" extends="struts-default" namespace="/">
    5     </package>
    6 </struts>

      接下来,我们要在Web应用的配置文件web.xml中配置Struts2的过滤器StrutsPrepareAndExecuteFilter,用来过滤所有的请求,配置文件如下:

     1 <?xml version="1.0" encoding="UTF-8"?>
     2 <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" version="3.0">
     3   <display-name>ssh7</display-name>
     4   <filter>
     5       <filter-name>struts2</filter-name>
     6       <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
     7   </filter>
     8   <filter-mapping>
     9       <filter-name>struts2</filter-name>
    10       <url-pattern>/*</url-pattern>
    11   </filter-mapping>
    12 </web-app>

      Struts2配置到这里就可以了,启动一下,看看成不成功。if 成功 then 继续下一步,else 检查一下导的包对不对,或者配置文件的路径和内容有没有错。

    3. 配置Spring

      首先,导入Spring所需的Jar包(先不考虑整合所需的包)。

      接下来,创建Spring配置文件,我创建的配置文件为applicationContext.xml,放在/WEB-INF目录下。配置文件如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd ">
    </beans>

    4. 整合Struts2与Spring

      1)在web.xml配置文件中配置一个上下文初始化参数(context-param),配置片段如下:

    <context-param>
          <param-name>contextConfigLocation</param-name><!--这个参数用于指定Spring配置文件的位置-->
          <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>

           2)web.xml配置文件中配置监听器org.springframework.web.context.ContextLoaderListener,用于监听ServletContext的加载,配置文件片段如下:

    <listener>
      <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

      3)导入Struts2的Spring插件包struts2-spring-plugin-2.3.16.3.jar,这是一个Struts2的插件包,用于在Struts2中引入Spring。  

      4)启动服务器,看看成功没有。(注意:检查一下有没有把需要的包都复制到/WEB-INF/lib中)

    5. 创建一个测试用的Action,并在struts.xml和applicationContext.xml中配置,

      Action类:

    package way.blog.struts2spring.action;
    
    import com.opensymphony.xwork2.ActionSupport;
    
    public class TestAction extends ActionSupport{
    
        private static final long serialVersionUID = 1L;
    
        public TestAction() {
        }
        
        public String execute(){
            return "success";
        }
        
    }

      applicationContext.xml配置片段:

    <bean id="testAction" class="way.blog.struts2spring.action.TestAction" scope="prototype">
    </bean>

       struts.xml配置片段:

    <package name="test" extends="struts-default" namespace="/">
       <!-- 这里action的class属性我们不填实现类的类名,而是填这个action在Spring配置中的bean的名称 -->
       <action name="testAction" class="testAction" method="execute">
          <result name="success">/index.jsp</result>
       </action>
    </package>

      运行服务器并访问该Action,页面成功跳转到/index.jsp,说明Struts2和Spring整合成功。

     二、(重点)对关键配置的分析

      1. 整合过程中,web.xml里添加的初始化参数(context-param) contextConfigLocation,是用来指定Spring配置文件的位置的,在这个例子中,参数的值为/WEB-INF/applicationContext.xml,那这个参数是被谁使用的呢?我们可以猜到可能与web.xml中添加的监听器有关,接着往下看。

      2. web.xml中添加的监听器org.springframework.web.context.ContextLoaderListener,继承了ContextLoader类,实现了ServletContextListener接口,所以它是在监听Web应用的状态(启动和关闭),即监听ServletContext对象的状态(初始化和销毁),这里我查看Spring的源码:

      ContextLoaderListener类片段:

    /**
     * Initialize the root web application context.
     */
     @Override
     public void contextInitialized(ServletContextEvent event) {
         initWebApplicationContext(event.getServletContext());
    }

      监听器监听ServletContext的初始化,在初始化完成后调用contextInitialized方法,contextInitialized方法中调用了父类ContextLoader中的initWebApplicationContext方法,并把初始化完成的ServletContext对象作为参数传入,我们看看ContextLoader的源码:

      ContextLoader源码片段:

      1     /**
      2      * Initialize Spring's web application context for the given servlet context,
      3      * using the application context provided at construction time, or creating a new one
      4      * according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and
      5      * "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.
      6      * @param servletContext current servlet context
      7      * @return the new WebApplicationContext
      8      * @see #ContextLoader(WebApplicationContext)
      9      * @see #CONTEXT_CLASS_PARAM
     10      * @see #CONFIG_LOCATION_PARAM
     11      */
     12     public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
     13         if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
     14             throw new IllegalStateException(
     15                     "Cannot initialize context because there is already a root application context present - " +
     16                     "check whether you have multiple ContextLoader* definitions in your web.xml!");
     17         }
     18 
     19         Log logger = LogFactory.getLog(ContextLoader.class);
     20         servletContext.log("Initializing Spring root WebApplicationContext");
     21         if (logger.isInfoEnabled()) {
     22             logger.info("Root WebApplicationContext: initialization started");
     23         }
     24         long startTime = System.currentTimeMillis();
     25 
     26         try {
     27             // Store context in local instance variable, to guarantee that
     28             // it is available on ServletContext shutdown.
     29             if (this.context == null) {
     30                 this.context = createWebApplicationContext(servletContext);
     31             }
     32             if (this.context instanceof ConfigurableWebApplicationContext) {
     33                 ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
     34                 if (!cwac.isActive()) {
     35                     // The context has not yet been refreshed -> provide services such as
     36                     // setting the parent context, setting the application context id, etc
     37                     if (cwac.getParent() == null) {
     38                         // The context instance was injected without an explicit parent ->
     39                         // determine parent for root web application context, if any.
     40                         ApplicationContext parent = loadParentContext(servletContext);
     41                         cwac.setParent(parent);
     42                     }
     43                     configureAndRefreshWebApplicationContext(cwac, servletContext);
     44                 }
     45             }
     46             servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
     47 
     48             ClassLoader ccl = Thread.currentThread().getContextClassLoader();
     49             if (ccl == ContextLoader.class.getClassLoader()) {
     50                 currentContext = this.context;
     51             }
     52             else if (ccl != null) {
     53                 currentContextPerThread.put(ccl, this.context);
     54             }
     55 
     56             if (logger.isDebugEnabled()) {
     57                 logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
     58                         WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
     59             }
     60             if (logger.isInfoEnabled()) {
     61                 long elapsedTime = System.currentTimeMillis() - startTime;
     62                 logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
     63             }
     64 
     65             return this.context;
     66         }
     67         catch (RuntimeException ex) {
     68             logger.error("Context initialization failed", ex);
     69             servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
     70             throw ex;
     71         }
     72         catch (Error err) {
     73             logger.error("Context initialization failed", err);
     74             servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
     75             throw err;
     76         }
     77     }    
     78 
     79   protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
     80         if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
     81             // The application context id is still set to its original default value
     82             // -> assign a more useful id based on available information
     83             String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
     84             if (idParam != null) {
     85                 wac.setId(idParam);
     86             }
     87             else {
     88                 // Generate default id...
     89                 wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
     90                         ObjectUtils.getDisplayString(sc.getContextPath()));
     91             }
     92         }
     93 
     94         wac.setServletContext(sc);
     95         String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
     96         if (configLocationParam != null) {
     97             wac.setConfigLocation(configLocationParam);
     98         }
     99 
    100         // The wac environment's #initPropertySources will be called in any case when the context
    101         // is refreshed; do it eagerly here to ensure servlet property sources are in place for
    102         // use in any post-processing or initialization that occurs below prior to #refresh
    103         ConfigurableEnvironment env = wac.getEnvironment();
    104         if (env instanceof ConfigurableWebEnvironment) {
    105             ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
    106         }
    107 
    108         customizeContext(sc, wac);
    109         wac.refresh();
    110     }

       源码看起来很复杂,但是只是出于理解的目的,所以我们只看重点的地方。 

      以上第30行处创建了一个WebApplicationContext对象(其实,WebApplicationContext只是这个对象实现的接口之一,通过第33行的类型强转我们可以看出这个对象还实现了ConfigurableWebApplicationContext接口),这个对象就是Spring的上下文对象,通过这个对象我们可以获取Spring中定义的Bean(还记得单独使用Spring时用的ClassPathXmlApplicationContext或FileSystemXmlApplicationContext吗?)。

      第43行处的configureAndRefreshWebApplicationContext方法使用ServletContext对象对WebApplicationContext对象进行配置。

      第95行处获取了ServletContext中的初始化参数CONFIG_LOCATION_PARAM,并在97行将该参数的值设置为WebApplicationContext的配置文件位置。其实常量CONFIG_LOCATION_PARAM就是字符串"contextConfigLocation",也就是说web.xml中配置的初始化参数在这里被用到了,这也印证了我们上面的猜想。

      创建并配置了Spring的上下文对象之后,在46行处,ServletContext对象将该WebApplicationContext对象设置为自己的一个属性,属性名为WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE。

      剩下的就是异常处理的代码了。

      我们发现,在ContextLoaderListener监听到ServletContext初始化完成后,只不过是创建了一个Spring的上下文对象,并将其设置为ServletContext对象的一个属性而已。我们可以知道的是,这个Spring的上下文对象肯定会在创建Action对象的时候被用到,但是Struts2是在什么时候获取这个对象,并在哪里使用这个对象来获取Action对象的bean呢?

      3. 为了确定Action对象的创建时机,我使用了一个小技巧,我在TestAction的构造方法中手动抛出了一个异常,这样我就可以根据异常信息跟踪调用创建Action对象的方法路径了。

        修改后的TestAction构造方法: 

    1     public TestAction() throws Exception{
    2         throw new Exception("创建Action对象时手动抛出的异常");
    3     }

        访问TestAction时的异常信息:

     1 严重: Exception occurred during processing request: Unable to instantiate Action, testAction,  defined for 'testAction' in namespace '/'Error creating bean with name 'testAction' defined in ServletContext resource [/WEB-INF/applicationContext.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [way.blog.struts2spring.action.TestAction]: Constructor threw exception; nested exception is java.lang.Exception: 创建Action对象时手动抛出的异常
     2 Unable to instantiate Action, testAction,  defined for 'testAction' in namespace '/'Error creating bean with name 'testAction' defined in ServletContext resource [/WEB-INF/applicationContext.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [way.blog.struts2spring.action.TestAction]: Constructor threw exception; nested exception is java.lang.Exception: 创建Action对象时手动抛出的异常
     3     at com.opensymphony.xwork2.DefaultActionInvocation.createAction(DefaultActionInvocation.java:316)
     4     at com.opensymphony.xwork2.DefaultActionInvocation.init(DefaultActionInvocation.java:397)
     5     at com.opensymphony.xwork2.DefaultActionProxy.prepare(DefaultActionProxy.java:194)
     6     at org.apache.struts2.impl.StrutsActionProxy.prepare(StrutsActionProxy.java:63)
     7     at org.apache.struts2.impl.StrutsActionProxyFactory.createActionProxy(StrutsActionProxyFactory.java:37)
     8     at com.opensymphony.xwork2.DefaultActionProxyFactory.createActionProxy(DefaultActionProxyFactory.java:58)
     9     at org.apache.struts2.dispatcher.Dispatcher.serviceAction(Dispatcher.java:552)
    10     at org.apache.struts2.dispatcher.ng.ExecuteOperations.executeAction(ExecuteOperations.java:77)
    11     at org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter.doFilter(StrutsPrepareAndExecuteFilter.java:99)
    12     at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
    13     at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    14     at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
    15     at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
    16     at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:501)
    17     at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170)
    18     at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:98)
    19     at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:950)
    20     at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
    21     at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
    22     at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1040)
    23     at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:607)
    24     at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:313)
    25     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    26     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    27     at java.lang.Thread.run(Thread.java:745)
    28 Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'testAction' defined in ServletContext resource [/WEB-INF/applicationContext.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [way.blog.struts2spring.action.TestAction]: Constructor threw exception; nested exception is java.lang.Exception: 创建Action对象时手动抛出的异常
    29     at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1101)
    30     at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1046)
    31     at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:504)
    32     at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
    33     at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322)
    34     at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
    35     at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:956)
    36     at com.opensymphony.xwork2.spring.SpringObjectFactory.buildBean(SpringObjectFactory.java:151)
    37     at com.opensymphony.xwork2.ObjectFactory.buildBean(ObjectFactory.java:171)
    38     at com.opensymphony.xwork2.factory.DefaultActionFactory.buildAction(DefaultActionFactory.java:22)
    39     at com.opensymphony.xwork2.ObjectFactory.buildAction(ObjectFactory.java:141)
    40     at com.opensymphony.xwork2.DefaultActionInvocation.createAction(DefaultActionInvocation.java:297)
    41     ... 24 more
    42 Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [way.blog.struts2spring.action.TestAction]: Constructor threw exception; nested exception is java.lang.Exception: 创建Action对象时手动抛出的异常
    43     at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:163)
    44     at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:89)
    45     at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1094)
    46     ... 35 more
    47 Caused by: java.lang.Exception: 创建Action对象时手动抛出的异常
    48     at way.blog.struts2spring.action.TestAction.<init>(TestAction.java:13)
    49     at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    50     at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
    51     at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    52     at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
    53     at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:147)
    54     ... 37 more

       异常信息很长,但是我们可以过滤掉一些跟我们本次分析无关的,只保留与Struts2和Spring有关的信息,即红色和蓝色字体部分。

      通过第11行到第3行,我们可以看出从StrutsPrepareAndExecuteFilter的doFilter方法开始如何通过一层层调用来创建Action对象,意料之中的是,我们发现了Struts2和Spring的衔接点,即上面的35-37行(蓝色字体),Struts2调用了Spring上下文对象(AbstractApplicationContext对象,猜测应该就是上面ContextLoaderListener中创建的Spring上下文对象转换来的,下面将验证这一点)的getBean方法!!!

      Struts2中的一个对象引起了我们的注意——SpringObjectFactory,正是这个对象的buildBean方法完成了对Spring上下文对象的调用。我们继续查看源码:

      com.opensymphony.xwork2.SpringObjectFactory源码片段:

     1 /**
     2  * Simple implementation of the ObjectFactory that makes use of Spring's application context if one has been configured,
     3  * before falling back on the default mechanism of instantiating a new class using the class name. <p/> In order to use
     4  * this class in your application, you will need to instantiate a copy of this class and set it as XWork's ObjectFactory
     5  * before the xwork.xml file is parsed. In a servlet environment, this could be done using a ServletContextListener.
     6  *
     7  * @author Simon Stewart (sms@lateral.net)
     8  */
     9 public class SpringObjectFactory extends ObjectFactory implements ApplicationContextAware {
    10     private static final Logger LOG = LoggerFactory.getLogger(SpringObjectFactory.class);
    11 
    12     protected ApplicationContext appContext;
    13 /*
    14  *      省略............  
    15  */
    16     /**
    17      * Set the Spring ApplicationContext that should be used to look beans up with.
    18      *
    19      * @param appContext The Spring ApplicationContext that should be used to look beans up with.
    20      */
    21     public void setApplicationContext(ApplicationContext appContext)
    22             throws BeansException {
    23         this.appContext = appContext;
    24         autoWiringFactory = findAutoWiringBeanFactory(this.appContext);
    25     }
    26 /*
    27  *      省略............  
    28  */
    29     /**
    30      * Looks up beans using Spring's application context before falling back to the method defined in the {@link
    31      * ObjectFactory}.
    32      *
    33      * @param beanName     The name of the bean to look up in the application context
    34      * @param extraContext
    35      * @return A bean from Spring or the result of calling the overridden
    36      *         method.
    37      * @throws Exception
    38      */
    39     @Override
    40     public Object buildBean(String beanName, Map<String, Object> extraContext, boolean injectInternal) throws Exception {
    41         Object o;
    42         
    43         if (appContext.containsBean(beanName)) {
    44             o = appContext.getBean(beanName);
    45         } else {
    46             Class beanClazz = getClassInstance(beanName);
    47             o = buildBean(beanClazz, extraContext);
    48         }
    49         if (injectInternal) {
    50             injectInternalBeans(o);
    51         }
    52         return o;
    53     }
    54 /*
    55  *      省略............  
    56  */
    57 }

      以上只截取了SpringObjectFactory中与我们分析有关的部分。我们来分析一下,首先,这个类继承了Struts2的ObjectFactory类,而通过查看文档中的说明或者源码(这里就不粘出来了),我们可以发现,这个ObjectFactory类是Struts2很重要的一个类,它用于创建所有Struts2的核心对象,包括Action, Interceptor, Result等。而SpringObjectFactory继承了ObjectFactory,说明通过将Struts2默认的ObjectFactory类替换为SpringObjectFactory就可以实现由Spring来创建对象了。

      看一看SpringObjectFactory的源码和注释,buildBean(String,Map<String,Object>,boolean)方法覆盖了ObjectFactory中的对应方法,它接收的第一个参数,即是我们在struts.xml配置文件中为action指定的class属性。我们看到,这个方法首先是尝试从appContext中获取对应名称的bean,如果失败,才把该名称当做类名去创建对象。还记得我们前面的一个问题吗?Struts2怎么知道什么时候把action配置中的class属性当做bean的名称,什么时候又把它当做类名?这里就是答案了。我们从方法注释上也可以看到,该方法先尝试从Spring的上下文中获取对应名称的对象,如果失败,才使用父类的方法根据类名去创建新的对象。

      谜团已经逐步解开,但是还有一个问题。注意,SpringObjectFactory中的ApplicationContext对象appContext是通过setApplicationContext方法传入的,那是由谁传入的?传入的是不是前面在ContextLoaderListener中创建的那个WebApplicationContext对象呢?

      4. 我为了解决上面的问题想了很久,最后才发现,我一直忽略了之前导入的struts2-spring-plugin-xxx.jar这个包,也许这就是问题的答案了。通过查看该包,发现一个StrutsSpringObjectFactory类,这个类继承了上面提到的SpringObjectFactory,

      org.apache.struts2.spring.StrutsSpringObjectFactory源码片段:

      1 /**
      2  * Struts object factory that integrates with Spring.
      3  * <p/>
      4  * Spring should be loaded using a web context listener
      5  * <code>org.springframework.web.context.ContextLoaderListener</code> defined in <code>web.xml</code>.
      6  *
      7  */
      8 public class StrutsSpringObjectFactory extends SpringObjectFactory {
      9     private static final Logger LOG = LoggerFactory.getLogger(StrutsSpringObjectFactory.class);
     10     /*
     11      *省略...
     12      */
     13     /**
     14      * Constructs the spring object factory
     15      * @param autoWire The type of autowiring to use
     16      * @param alwaysAutoWire Whether to always respect the autowiring or not
     17      * @param useClassCacheStr Whether to use the class cache or not
     18      * @param servletContext The servlet context
     19      * @since 2.1.3
     20      */
     21     @Inject
     22     public StrutsSpringObjectFactory(
     23             @Inject(value=StrutsConstants.STRUTS_OBJECTFACTORY_SPRING_AUTOWIRE,required=false) String autoWire,
     24             @Inject(value=StrutsConstants.STRUTS_OBJECTFACTORY_SPRING_AUTOWIRE_ALWAYS_RESPECT,required=false) String alwaysAutoWire,
     25             @Inject(value=StrutsConstants.STRUTS_OBJECTFACTORY_SPRING_USE_CLASS_CACHE,required=false) String useClassCacheStr,
     26             @Inject ServletContext servletContext,
     27             @Inject(StrutsConstants.STRUTS_DEVMODE) String devMode,
     28             @Inject Container container) {
     29           
     30         super();
     31         boolean useClassCache = "true".equals(useClassCacheStr);
     32         if (LOG.isInfoEnabled()) {
     33             LOG.info("Initializing Struts-Spring integration...");
     34         }
     35 
     36         Object rootWebApplicationContext =  servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
     37 
     38         if(rootWebApplicationContext instanceof RuntimeException){
     39             RuntimeException runtimeException = (RuntimeException)rootWebApplicationContext;
     40             LOG.fatal(runtimeException.getMessage());
     41             return;
     42         }
     43 
     44         ApplicationContext appContext = (ApplicationContext) rootWebApplicationContext;
     45         if (appContext == null) {
     46             // uh oh! looks like the lifecycle listener wasn't installed. Let's inform the user
     47             String message = "********** FATAL ERROR STARTING UP STRUTS-SPRING INTEGRATION **********
    " +
     48                     "Looks like the Spring listener was not configured for your web app! 
    " +
     49                     "Nothing will work until WebApplicationContextUtils returns a valid ApplicationContext.
    " +
     50                     "You might need to add the following to web.xml: 
    " +
     51                     "    <listener>
    " +
     52                     "        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    " +
     53                     "    </listener>";
     54             LOG.fatal(message);
     55             return;
     56         }
     57         
     58         String watchList = container.getInstance(String.class, "struts.class.reloading.watchList");
     59         String acceptClasses = container.getInstance(String.class, "struts.class.reloading.acceptClasses");
     60         String reloadConfig = container.getInstance(String.class, "struts.class.reloading.reloadConfig");
     61 
     62         if ("true".equals(devMode)
     63                 && StringUtils.isNotBlank(watchList)
     64                 && appContext instanceof ClassReloadingXMLWebApplicationContext) {
     65             //prevent class caching
     66             useClassCache = false;
     67 
     68             ClassReloadingXMLWebApplicationContext reloadingContext = (ClassReloadingXMLWebApplicationContext) appContext;
     69             reloadingContext.setupReloading(watchList.split(","), acceptClasses, servletContext, "true".equals(reloadConfig));
     70             if (LOG.isInfoEnabled()) {
     71             LOG.info("Class reloading is enabled. Make sure this is not used on a production environment!", watchList);
     72             }
     73 
     74             setClassLoader(reloadingContext.getReloadingClassLoader());
     75 
     76             //we need to reload the context, so our isntance of the factory is picked up
     77             reloadingContext.refresh();
     78         }
     79 
     80         this.setApplicationContext(appContext);
     81 
     82         int type = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME;   // default
     83         if ("name".equals(autoWire)) {
     84             type = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME;
     85         } else if ("type".equals(autoWire)) {
     86             type = AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE;
     87         } else if ("auto".equals(autoWire)) {
     88             type = AutowireCapableBeanFactory.AUTOWIRE_AUTODETECT;
     89         } else if ("constructor".equals(autoWire)) {
     90             type = AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR;
     91         } else if ("no".equals(autoWire)) {
     92             type = AutowireCapableBeanFactory.AUTOWIRE_NO;
     93         }
     94         this.setAutowireStrategy(type);
     95 
     96         this.setUseClassCache(useClassCache);
     97 
     98         this.setAlwaysRespectAutowireStrategy("true".equalsIgnoreCase(alwaysAutoWire));
     99 
    100         if (LOG.isInfoEnabled()) {
    101             LOG.info("... initialized Struts-Spring integration successfully");
    102         }
    103     }
    104 }

      这个类中只有一个方法,就是构造方法,在36行处我们惊奇的发现,我们之前存入ServletContext对象中的属性ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE终于又出现了!!!该属性的值就是在ContextLoaderListener中创建的那个Spring上下文对象,这里将其获取出来,并在第80行处调用了父类,即SpringObjectFactory的setApplicationContext方法将其赋值给继承自父类的ApplicationContext类型成员变量appContext。到这里就解决了上面问题的,我们证明了SpringObjectFactory中用到的ApplicationContext对象就是之前ContextLoaderListener中创建的,而且该对象由StrutsSpringObjectFactory的构造方法中调用父类的setApplicationContext方法传入。

      5. 通过上面的分析我们确定了最后会用StrutsSpringObjectFactory类代替Struts中原来的ObjectFactory。那么是在哪里发生替换的呢?我先看了看struts核心包中的struts-default.xml文件,发现了我们要找的默认的ObjectFactory的定义:

      struts-default.xml片段:

      <bean class="com.opensymphony.xwork2.ObjectFactory" name="struts"/>

      我们再查看struts核心包中的default.properties文件中定义的常量,我们找到了这一段:

    1 ### if specified, the default object factory can be overridden here
    2 ### Note: short-hand notation is supported in some cases, such as "spring"
    3 ###       Alternatively, you can provide a com.opensymphony.xwork2.ObjectFactory subclass name here
    4 # struts.objectFactory = spring

      也就是说只要我们将struts.objectFactory常量的值覆盖,换成我们自己定义的ObjectFactory对象,就可以覆盖原来的默认ObjectFactory了。我们再看看struts2-spring-plugin-xxx.jar插件包中的struts-plugin.xml文件,真相大白了!!!!

      struts-plugin.xml文件片段: 

    <bean type="com.opensymphony.xwork2.ObjectFactory" name="spring" class="org.apache.struts2.spring.StrutsSpringObjectFactory" />
        
    <!--  Make the Spring object factory the automatic default -->
    <constant name="struts.objectFactory" value="spring" />

      我们看到这里定义了一个名为"spring"的ObjectFactory对象,其实现类正是StrtutsSpringObjectFactory,并且接下来设置了struts.objectFactory常量,将其设置成了我们定义的"spring"对象。

      我们知道,Struts2在加载配置文件的时候会在Classpath中的寻找struts-plugin.xml文件,并自动将其加载,这样就完成了将Struts2与Spring的整合了。

    ----------------------------------------------------------------------------

      这个过程我也是花了很久才整理出来的,因为忘记了struts-plugin.xml是自动加载的,所以我没有从struts-plugin.xml作为突破口,不然可能整个过程会顺利许多。不过这样一个探索的过程也是挺有趣的。这个过程中难免有些疏漏,或者说明不清楚,就请大家多多指教了,毕竟这是我第一篇博客,哈哈哈!!!

      文章是原创的,大家可以自由修改完善并转发,转发时请注明出处,,谢谢。

  • 相关阅读:
    倒计时
    用css 添加手状样式,鼠标移上去变小手
    二维数组去重方法
    权限管理
    文件操作
    【十一章】:RabbitMQ队列
    【十一章】:Memcache、Redis
    【第十章】:I/O多路复用、异步I/O(综合篇)
    【模块】:paramiko
    【第九章】:线程、进程和协程
  • 原文地址:https://www.cnblogs.com/waychan/p/4735187.html
Copyright © 2020-2023  润新知