2013年1月9日 星期三

Struts2 Spring Plugin 詳解

打開struts2-spring-plugin-2.3.8.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" />
這設定就讓struts2-spring-plugin-2.3.8.jar放入你的/WEB-INF/lib時自動將預設的object factory改用StrutsSpringObjectFactory。

再仔細看看StrutsSpringObjectFactory可以發現它繼承了com.opensymphony.xwork2.spring.SpringObjectFactory,這個class最重要的是它override了兩個名為buildBean的method。

首先先看以下這個method,
代碼:
@Override
public Object buildBean(String beanName, Map<String, Object> extraContext, boolean injectInternal) throws Exception {
    Object o;
  
    if (appContext.containsBean(beanName)) {
        o = appContext.getBean(beanName);
    } else {
        Class beanClazz = getClassInstance(beanName);
        o = buildBean(beanClazz, extraContext);
    }
    if (injectInternal) {
        injectInternalBeans(o);
    }
    return o;
}
這個method就是Struts2主要會用來create Action、Interceptor、Result等bean instance的入口method,從程式我們可以發現SpringObjectFactory會先判斷Spring是否有這個bean,如果有就直接使用;如果沒有,才抓出class name,並且呼叫另外一個buildBean method。

接下來看另外一個method,
代碼:
@Override
public Object buildBean(Class clazz, Map<String, Object> extraContext) throws Exception {
    Object bean;

    try {
        // Decide to follow autowire strategy or use the legacy approach which mixes injection strategies
        if (alwaysRespectAutowireStrategy) {
            // Leave the creation up to Spring
            bean = autoWiringFactory.createBean(clazz, autowireStrategy, false);
            injectApplicationContext(bean);
            return injectInternalBeans(bean);
        } else {
            bean = autoWiringFactory.autowire(clazz, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false);
            bean = autoWiringFactory.applyBeanPostProcessorsBeforeInitialization(bean, bean.getClass().getName());
            // We don't need to call the init-method since one won't be registered.
            bean = autoWiringFactory.applyBeanPostProcessorsAfterInitialization(bean, bean.getClass().getName());
            return autoWireBean(bean, autoWiringFactory);
        }
    } catch (UnsatisfiedDependencyException e) {
        if (LOG.isErrorEnabled())
            LOG.error("Error building bean", e);
        // Fall back
        return autoWireBean(super.buildBean(clazz, extraContext), autoWiringFactory);
    }
}
這個method稍微複雜一點,但不難發現它是用來inject Spring beans到新生成的Struts2 bean之中。

從上面兩個method可以知道,Struts2 Spring Plugin會先嘗試從Spring拿到所需要的bean,如果拿不到,才會自己生成bean,然後再inject所需要的Spring beans,這就是為什麼在struts.xml中將Action class name設定為Spring bean name可以運作的原因,即使在Action依舊設定 full class name,也會執行injection的動作,讓Action可以拿到Spring beans。

最後要提醒的是,只要是Struts2的bean都會這樣生成,無論是Action、Interceptor、Result等,將其轉給Spring控管後,須小心thread safe的問題,因為Spring預設scope是singleton。