struts2默认拦截器之autowiring

也许不会看见 2011-12-31

   在struts2的struts-default.xml中定义了一个name为autowiring拦截器,实现类是com.opensymphony.xwork2.spring.interceptor.ActionAutowiringInterceptor,它的作用是在struts2和spring整合时为action注入spring上下文ApplicationContext(Action需要实现org.springframework.context.ApplicationContextAware接口),并使用com.opensymphony.xwork2.inject.Container对象为action注入其他属性。 

首先说明一下struts2与spring的整合。

    要实现struts2与spring的整合,只需将struts2-spring-plugin-2.x.x.jar加入到项目中即可。在该jar包中存在文件struts-plugin.xml,在struts2启动时会被加载,文件内容如下:
<struts>
    <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" />
    <constant name="struts.class.reloading.watchList" value="" />
    <constant name="struts.class.reloading.acceptClasses" value="" />
    <constant name="struts.class.reloading.reloadConfig" value="false" />
    <package name="spring-default">
        <interceptors>
            <interceptor name="autowiring" class ="com.opensymphony.xwork2.spring.interceptor.ActionAutowiringInterceptor"/>
        </interceptors>
    </package>
</struts>

     这里它将struts2框架常量struts.objectFactory设置为"spring",这里其实是使用了缩写形式,全称是"org.apache.struts2.spring.StrutsSpringObjectFactory"。这个缩写的"spring"是和bean配置中的name属性相对应的。默认情况下struts2框架创建的对象都是由ObjectFactory实例化的,ObjectFactory提供了与其他IoC容器(如Spring等)集成的方法。覆盖这个ObjectFactory的类必须继承ObjectFactory类或者它的任何子类,并且带有一个无参构造方法或者构造方法中的参数全部使用struts2的@Inject注解。上述代码使用org.apache.struts2.spring.StrutsSpringObjectFactory代替了默认的ObjectFactory。该ObjectFactory使得struts2中的bean可以由spring来装配,默认情况下框架使用的自动装配策略是name,也就是说框架会根据spring中bean的name属性自动装配,可选的装配策略还有type、auto、constructor,我们可以根据常量struts.objectFactory.spring.autoWire来进行设置。 

    在struts2与spring进行整合的时候还需要在web.xml中添加如下信息来启动spring:

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

 但如果没有配置这个会出现什么情况呢?如果未配置上述代码,启动web服务器会出现如下错误FATAL 2011-12-28 11:53:34:272 - ********** FATAL ERROR STARTING UP

STRUTS-SPRING INTEGRATION **********
Looks like the Spring listener was not configured for your web app!
Nothing will work until WebApplicationContextUtils returns a valid ApplicationContext.
You might need to add the following to web.xml: 
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
ERROR 2011-12-28 11:53:34:288 - Dispatcher initialization failed
java.lang.NullPointerException
        at com.opensymphony.xwork2.spring.SpringObjectFactory.getClassInstance(SpringObjectFactory.java:220)
        …………

     错误中显示是出现了NullPointerException(SpringObjectFactory.java:220), 出现该异常的代码是:

if(appContext.containsBean(className))

    此处的appContext是org.springframework.context.ApplicationContext,说明在SpringObjectFactory类(StrutsSpringObjectFactory的直接父类)中需要存在org.springframework.context.ApplicationContext对象的引用,此处的 ApplicationContext需要什么时候进行装配呢?在StrutsSpringObjectFactory类中只存在一个构造方法

public class StrutsSpringObjectFactory extends SpringObjectFactory {
    private static final Logger LOG = LoggerFactory.getLogger(StrutsSpringObjectFactory.class);

    //@Inject
    //public StrutsSpringObjectFactory(
    //        @Inject(value=StrutsConstants.STRUTS_OBJECTFACTORY_SPRING_AUTOWIRE,required=false) String autoWire,
    //        @Inject(value=StrutsConstants.STRUTS_OBJECTFACTORY_SPRING_USE_CLASS_CACHE,required=false) String useClassCacheStr,
    //        @Inject ServletContext servletContext) {
    //    this(autoWire, "false", useClassCacheStr, servletContext);
    //}

    /**
     * Constructs the spring object factory
     * @param autoWire The type of autowiring to use
     * @param alwaysAutoWire Whether to always respect the autowiring or not
     * @param useClassCacheStr Whether to use the class cache or not
     * @param servletContext The servlet context
     * @since 2.1.3
     */
    @Inject
    public StrutsSpringObjectFactory(
            @Inject(value=StrutsConstants.STRUTS_OBJECTFACTORY_SPRING_AUTOWIRE,required=false) String autoWire,
            @Inject(value=StrutsConstants.STRUTS_OBJECTFACTORY_SPRING_AUTOWIRE_ALWAYS_RESPECT,required=false) String alwaysAutoWire,
            @Inject(value=StrutsConstants.STRUTS_OBJECTFACTORY_SPRING_USE_CLASS_CACHE,required=false) String useClassCacheStr,
            @Inject ServletContext servletContext,
            @Inject(StrutsConstants.STRUTS_DEVMODE) String devMode,
            @Inject Container container) {
          
        super();
        boolean useClassCache = "true".equals(useClassCacheStr);
        LOG.info("Initializing Struts-Spring integration...");

        Object rootWebApplicationContext =  servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);

        if(rootWebApplicationContext instanceof RuntimeException){
            RuntimeException runtimeException = (RuntimeException)rootWebApplicationContext;
            LOG.fatal(runtimeException.getMessage());
            return;
        }

        ApplicationContext appContext = (ApplicationContext) rootWebApplicationContext;
        if (appContext == null) {
            // uh oh! looks like the lifecycle listener wasn't installed. Let's inform the user
            String message = "********** FATAL ERROR STARTING UP STRUTS-SPRING INTEGRATION **********\n" +
                    "Looks like the Spring listener was not configured for your web app! \n" +
                    "Nothing will work until WebApplicationContextUtils returns a valid ApplicationContext.\n" +
                    "You might need to add the following to web.xml: \n" +
                    "    <listener>\n" +
                    "        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>\n" +
                    "    </listener>";
            LOG.fatal(message);
            return;
        }
        
        String watchList = container.getInstance(String.class, "struts.class.reloading.watchList");
        String acceptClasses = container.getInstance(String.class, "struts.class.reloading.acceptClasses");
        String reloadConfig = container.getInstance(String.class, "struts.class.reloading.reloadConfig");

        if ("true".equals(devMode)
                && StringUtils.isNotBlank(watchList)
                && appContext instanceof ClassReloadingXMLWebApplicationContext) {
            //prevent class caching
            useClassCache = false;

            ClassReloadingXMLWebApplicationContext reloadingContext = (ClassReloadingXMLWebApplicationContext) appContext;
            reloadingContext.setupReloading(watchList.split(","), acceptClasses, servletContext, "true".equals(reloadConfig));
            LOG.info("Class reloading is enabled. Make sure this is not used on a production environment!", watchList);

            setClassLoader(reloadingContext.getReloadingClassLoader());

            //we need to reload the context, so our isntance of the factory is picked up
            reloadingContext.refresh();
        }

        this.setApplicationContext(appContext);

        int type = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME;   // default
        if ("name".equals(autoWire)) {
            type = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME;
        } else if ("type".equals(autoWire)) {
            type = AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE;
        } else if ("auto".equals(autoWire)) {
            type = AutowireCapableBeanFactory.AUTOWIRE_AUTODETECT;
        } else if ("constructor".equals(autoWire)) {
            type = AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR;
        } else if ("no".equals(autoWire)) {
            type = AutowireCapableBeanFactory.AUTOWIRE_NO;
        }
        this.setAutowireStrategy(type);

        this.setUseClassCache(useClassCache);

        this.setAlwaysRespectAutowireStrategy("true".equalsIgnoreCase(alwaysAutoWire));

        LOG.info("... initialized Struts-Spring integration successfully");
    }
}
 

 在上述代码中从servletContext中取到一个名称为"org.springframework.web.context.WebApplicationContext.ROOT"的属性值,将其强制转换为ApplicationContext类型,之后调用SpringObjectFactory类setApplicationContext方法将spring上下文传递给了SpringObjectFactory。spring上下文是什么时候放置到servletContext中的呢?我们接下来看一下org.springframework.web.context.ContextLoaderListener类,这个监听器到底做了什么? 我们知道ContextLoaderListener是在服务器初始化的时候执行contextInitialized这个方法,该方法的代码如下:

public void contextInitialized(ServletContextEvent event) {
    this.contextLoader = createContextLoader();
    this.contextLoader.initWebApplicationContext(event.getServletContext());
}

 可以看出主要起作用的应该是initWebApplicationContext方法,初始化WebApplicationContext,我们进去看看:

public WebApplicationContext initWebApplicationContext(ServletContext servletContext)
			throws IllegalStateException, BeansException {

		if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
			throw new IllegalStateException(
					"Cannot initialize context because there is already a root application context present - " +
					"check whether you have multiple ContextLoader* definitions in your web.xml!");
		}

		servletContext.log("Initializing Spring root WebApplicationContext");
		if (logger.isInfoEnabled()) {
			logger.info("Root WebApplicationContext: initialization started");
		}
		long startTime = System.currentTimeMillis();

		try {
			// Determine parent for root web application context, if any.
			ApplicationContext parent = loadParentContext(servletContext);

			// Store context in local instance variable, to guarantee that
			// it is available on ServletContext shutdown.
			this.context = createWebApplicationContext(servletContext, parent);
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
			currentContextPerThread.put(Thread.currentThread().getContextClassLoader(), this.context);

			if (logger.isDebugEnabled()) {
				logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
						WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
			}
			if (logger.isInfoEnabled()) {
				long elapsedTime = System.currentTimeMillis() - startTime;
				logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
			}

			return this.context;
		}
		catch (RuntimeException ex) {
			logger.error("Context initialization failed", ex);
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
			throw ex;
		}
		catch (Error err) {
			logger.error("Context initialization failed", err);
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
			throw err;
		}
	}

     上面代码,将spring上下文放置到了servletContext中,因此只要通过org.springframework.web.context.ContextLoaderListener类启动了spring,那么在struts2中通过servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)或者ActionContext.getContext().getApplication().get(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)就能取到spring上下文对象

    spring把ApplicationContext放在application里面,然后struts2在application中把这个对象取出来,放在SpringObjectFactory里面,然后struts2与spring就实现了集成,当我们没有在web.xml里面配置监听器,也就是没有把ApplicationContext的对象放在application里面,所以struts2去application里面去取的时候会是null,这就是为什么没有配置监听器那里会抛NullPointerException异常。

当struts2和spring集成之后,struts2将允许Spring创建Action、Interceptror和Result,并且由Struts创建的对象能够被Spring装配。这样就可以在配置action时不使用类名,而使用spring中bean的name值了。如果在spring中找不到与Action的class值相匹配的bean,struts2框架会根据class值初始化一个action对象,并对action对象进行装配。

    其实SpringObjectFactory已经对Action对象注入了ApplicationContext和其他struts2对象,那么autowiring拦截器岂不是没用了?有一种情况,当项目中同时使用了Struts2和spring,但未将struts.objectFactory的值设置为"spring",即Struts2的objectFactory还是默认的ObjectFactory类,这样action中就不会被注入spring的上下文对象,就需要使用autowiring拦截器了。autowiring拦截器会从application中获取到spring上下文对象,初始化一个SpringObjectFactory对象,使用该对象对Action对象进行装配。

相关推荐

sunh / 0评论 2013-06-19