本篇文章讓我們來詳細(xì)探討一下Struts2的配置文件的結(jié)構(gòu)、配置文件的各個(gè)節(jié)點(diǎn)和每個(gè)節(jié)點(diǎn)中元素的使用方式。
目 錄
[ - ]總攬模塊化管理配置文件簡單的IoCpackage節(jié)點(diǎn)詳解參考文檔總攬
Struts2的配置文件是以XML的形式出現(xiàn)的。不過它的XML的語義比較簡單,下面是我抽取了位于struts2-core-2.0.14.jar內(nèi)部的struts-default.xml的片段:
Xml代碼
<struts>
<bean class="com.opensymphony.xwork2.ObjectFactory" name="xwork" />
<bean type="com.opensymphony.xwork2.ObjectFactory" name="struts" class="org.apache.struts2.impl.StrutsObjectFactory" />
<bean type="com.opensymphony.xwork2.ActionProxyFactory" name="xwork" class="com.opensymphony.xwork2.DefaultActionProxyFactory"/>
<bean type="com.opensymphony.xwork2.ActionProxyFactory" name="struts" class="org.apache.struts2.impl.StrutsActionProxyFactory"/>
<!-- 省略了其他的bean節(jié)點(diǎn)的定義 -->
<!-- Only have static injections -->
<bean class="com.opensymphony.xwork2.ObjectFactory" static="true" />
<bean class="com.opensymphony.xwork2.util.XWorkConverter" static="true" />
<!-- 省略了其他的靜態(tài)注入的定義 -->
<package name="struts-default" abstract="true">
<result-types>
<result-type name="chain" class="com.opensymphony.xwork2.ActionChainResult"/>
<result-type name="dispatcher" class="org.apache.struts2.dispatcher.ServletDispatcherResult" default="true"/>
<result-type name="redirect" class="org.apache.struts2.dispatcher.ServletRedirectResult"/>
<!-- 省略了其他的ResultType的定義 -->
</result-types>
<interceptors>
<interceptor name="autowiring" class="com.opensymphony.xwork2.spring.interceptor.ActionAutowiringInterceptor"/>
<interceptor name="params" class="com.opensymphony.xwork2.interceptor.ParametersInterceptor"/>
<!-- 省略了其他的Interceptor的定義 -->
<!-- Basic stack -->
<interceptor-stack name="basicStack">
<interceptor-ref name="exception"/>
<interceptor-ref name="servletConfig"/>
<interceptor-ref name="prepare"/>
<interceptor-ref name="checkbox"/>
<interceptor-ref name="params"/>
<interceptor-ref name="conversionError"/>
</interceptor-stack>
<!-- A complete stack with all the common interceptors in place.
Generally, this stack should be the one you use, though it
may do more than you need. Also, the ordering can be
switched around (ex: if you wish to have your servlet-related
objects applied before prepare() is called, you'd need to move
servlet-config interceptor up.
This stack also excludes from the normal validation and workflow
the method names input, back, and cancel. These typically are
associated with requests that should not be validated.
-->
<interceptor-stack name="defaultStack">
<interceptor-ref name="exception"/>
<interceptor-ref name="alias"/>
<interceptor-ref name="servletConfig"/>
<interceptor-ref name="prepare"/>
<interceptor-ref name="i18n"/>
<interceptor-ref name="chain"/>
<interceptor-ref name="debugging"/>
<interceptor-ref name="profiling"/>
<interceptor-ref name="scopedModelDriven"/>
<interceptor-ref name="modelDriven"/>
<interceptor-ref name="fileUpload"/>
<interceptor-ref name="checkbox"/>
<interceptor-ref name="staticParams"/>
<interceptor-ref name="params">
<param name="excludeParams">dojo\..*</param>
</interceptor-ref>
<interceptor-ref name="conversionError"/>
<interceptor-ref name="validation">
<param name="excludeMethods">input,back,cancel,browse</param>
</interceptor-ref>
<interceptor-ref name="workflow">
<param name="excludeMethods">input,back,cancel,browse</param>
</interceptor-ref>
</interceptor-stack>
<!-- 省略了其他的interceptor-stack節(jié)點(diǎn)的定義 -->
</interceptors>
<default-interceptor-ref name="defaultStack"/>
</package>
</struts>
在這個(gè)配置文件中,我們可以看到,Struts2的XML自身所支持的節(jié)點(diǎn)和子節(jié)點(diǎn)并不是很多,大致來說,這些節(jié)點(diǎn)可以分成基本配置定義和Runtime配置定義。
基本配置定義
基本配置定義,主要是針對在Struts2內(nèi)部所使用的各種元素的聲明。這些聲明往往規(guī)定了Struts2內(nèi)部的一些行為特征。
例如,配置文件中的<bean>節(jié)點(diǎn),被用于定義Struts2中所使用的接口和實(shí)現(xiàn)類,通過Struts2內(nèi)部實(shí)現(xiàn)的IoC,你就可以在不同的實(shí)現(xiàn)類之間進(jìn)行切換。
再例如,配置文件中的<result-type>節(jié)點(diǎn)和<interceptor>節(jié)點(diǎn)。他們用于定義Struts2中所支持的所有的Result類型和攔截器,這些定義和聲明,將在Runtime的配置定義中被引用。
我之所以把配置文件中的這些節(jié)點(diǎn)單獨(dú)列出來,作為一個(gè)種類,是因?yàn)檫@些節(jié)點(diǎn)是不可省略的,也是無法簡化的。所以,如果我們試圖在Struts2中簡化配置,我們就需要在Runtime配置定義中下功夫,而這些基本配置定義,我們可以認(rèn)為是Runtime配置定義的基礎(chǔ)。
Runtime配置定義
Runtime配置定義,主要指的的是對Struts2運(yùn)行過程中,具體的某個(gè)Action的行為的指定。這些指定主要通過<package>節(jié)點(diǎn)中的<action>節(jié)點(diǎn)來完成。
仔細(xì)翻閱<action>節(jié)點(diǎn),我們可以發(fā)現(xiàn),它是URL與Action之間溝通的橋梁,也就是說,它定義了URL與Action之間的對應(yīng)關(guān)系。同時(shí),它還指定了Action在執(zhí)行過程中的具體行為,包括Action執(zhí)行的時(shí)候使用什么樣的攔截器、Action執(zhí)行完畢后,轉(zhuǎn)向到什么樣的Result等等。
Runtime配置定義是可以簡化的,Struts2中提供了很多種簡化配置的方式,這個(gè)在之后的文章中會詳細(xì)提到。
模塊化管理配置文件
一旦項(xiàng)目變得很大,項(xiàng)目中同時(shí)也并不采取什么簡化配置的措施,那么在默認(rèn)情況下,配置文件就會變得很大而不易于維護(hù)。這個(gè)時(shí)候,對于配置文件的模塊化管理的需求就顯現(xiàn)出來。Struts2提供了兩種方式對配置文件進(jìn)行模塊化管理。
plugin機(jī)制
Struts2有plugin的機(jī)制,有關(guān)plugin的具體的知識,請參考我的另外一篇專欄文章:《深入plugin》 ——
http://www.javaeye.com/wiki/struts2/1333-deep-into-plugin。在這里,我也就不詳細(xì)介紹了。
在每個(gè)plugin中,都會有一個(gè)叫做struts-plugin.xml的配置文件,這個(gè)配置文件的格式與struts-default.xml的格式是相同的??梢栽谄渲凶龀鋈魏蔚腟truts2的定義和配置。我們知道,Struts2的配置文件的加載順序,是按照以下的順序來:
Struts2 Referece 寫道
1. struts-default.xml (bundled in the Core JAR)
2. struts-plugin.xml (as many as can be found in other JARs)
3. struts.xml (provided by your application)
所以,struts-plugin.xml中的配置的效果實(shí)際上與struts-default.xml的效果是相同的。這樣,通過各種各樣不同的plugin,就等于將Struts2的配置,按照plugin的功能不同而分開了。從而起到了配置文件模塊化管理的效果。
使用include節(jié)點(diǎn)
plugin中的配置文件,實(shí)際上是位于classpath的JAR包中的,那么我們在項(xiàng)目中,如何對一個(gè)龐大的配置文件進(jìn)行拆分呢?在Struts2中,可以使用include節(jié)點(diǎn)對所有的Struts2配置文件進(jìn)行拆分和模塊化管理。例如:
Xml代碼
<struts>
<include file="struts-default.xml"/>
<include file="web/struts-config.xml"/>
<include file="web/struts-action.xml"/>
</struts>
其中,file所指定的文件是相對于classpath的相對目錄中的文件。而每個(gè)配置文件的格式與struts-default.xml的格式也是相同的。
通過include節(jié)點(diǎn),我們就可以對一個(gè)比較大的配置文件按照功能和模塊進(jìn)行拆分,這在一個(gè)大型的團(tuán)隊(duì)開發(fā)中,是相當(dāng)有意義的。
簡單的IoC
在基本配置定義中,有兩個(gè)很常用的節(jié)點(diǎn):<bean>和<constant>。在系統(tǒng)啟動的時(shí)候,Struts2會根據(jù)配置文件中這些<bean>和<constant>節(jié)點(diǎn)的定義進(jìn)行加載,并初始化成為Struts2的默認(rèn)行為。這種初始化的行為,非常類似于Spring中的依賴注入(IoC),從而使得你不再需要擔(dān)心這些對象在運(yùn)行時(shí)的創(chuàng)建和銷毀,所有的工作都由Struts2內(nèi)部的機(jī)制實(shí)現(xiàn)。接下來我們就來看看Struts2是如何實(shí)現(xiàn)IoC的。
Struts2 Reference 寫道
Internally, the framework uses its own dependency injection container that is very similar to Google Guice (both were originally developed by Bob Lee)
這是來自于Struts2的Reference對它自身的IoC的描述。如果熟悉Guice的朋友一定知道,Guice的實(shí)現(xiàn)使用了Annotation的方式進(jìn)行,而整個(gè)依賴注入的實(shí)現(xiàn),是通過一個(gè)內(nèi)部的容器類進(jìn)行的。Struts2的依賴注入,與Guice的機(jī)制完全一致。根據(jù)注入的內(nèi)容的不同,Struts2的IoC可以對容器中的對象的依賴關(guān)系進(jìn)行管理,也可以注入一些靜態(tài)變量。
bean注入
對于bean的注入,對應(yīng)于XML中的bean的節(jié)點(diǎn)聲明。我把其中的機(jī)制分成了3個(gè)部分:
1. 容器中對象的聲明
Xml代碼
<bean class="com.opensymphony.xwork2.ObjectFactory" name="xwork" />
<bean type="com.opensymphony.xwork2.ObjectFactory" name="struts" class="org.apache.struts2.impl.StrutsObjectFactory" />
這點(diǎn)沒什么好說的,在struts.xml中,你可以為某個(gè)接口聲明它所對應(yīng)的實(shí)現(xiàn)類。
name屬性
你可以聲明多個(gè)實(shí)現(xiàn)類,使用name屬性進(jìn)行區(qū)分。在注入的時(shí)候,將使用這個(gè)屬性的值作為接口實(shí)現(xiàn)類的選擇。
required屬性
你還可以通過required屬性,來指定是否在運(yùn)行時(shí)必不可少的注入。如果reqired被設(shè)置成false,那么當(dāng)不存在相應(yīng)的接口定義時(shí),注入將被忽略。
static屬性
在XML的定義中,還可以使用static屬性。如果static屬性被設(shè)置成true,那么注入將針對bean中的static方法和static屬性進(jìn)行。
2. 在代碼中使用Annotation進(jìn)行注入
Java代碼
@Inject("xwork")
protected ObjectFactory objectFactory;
public LightURLUnknownHandler(@Inject ObjectFactory objectFactory) {
this.objectFactory = objectFactory;
}
@Inject
public void setObjectFactory(ObjectFactory factory) {
this.objectFactory = factory;
}
@Inject(required=false)
public void setUnknownHandler(UnknownHandler handler) {
this.unknownHandler = handler;
}
在代碼中,使用@Inject這樣一個(gè)Annotation進(jìn)行對象依賴注入。在上面的例子中,我們可以看到,@Inject這個(gè)Annotation,可以作用在屬性上,也可以作用在方法上,甚至可以作用在方法的參數(shù)上。
在默認(rèn)情況下,如果@Inject不指定value,那么XML配置定義中的name="default"或者name=""的實(shí)現(xiàn)類定義將被注入。
那么,在struts-default.xml中,Struts2到底選擇了那些實(shí)現(xiàn)類,作為Struts2或者XWork內(nèi)部接口的默認(rèn)實(shí)現(xiàn)類呢?默認(rèn)情況下,struts-default.xml中定義的bean的name="struts"的將被作為默認(rèn)的接口實(shí)現(xiàn)類被注入。這些默認(rèn)行為,是由org.apache.struts2.config.BeanSelectionProvider所決定的,有興趣的讀者可以參閱這個(gè)類的源碼。
3. 內(nèi)部的Container機(jī)制完成一切背后工作
上面看到的,是現(xiàn)象。在內(nèi)部,Struts2通過一個(gè)Container來實(shí)現(xiàn)所有的注入機(jī)制。
Java代碼
public interface Container extends Serializable {
/**
* Default dependency name.
*/
String DEFAULT_NAME = "default";
/**
* Injects dependencies into the fields and methods of an existing object.
*/
void inject(Object o);
/**
* Creates and injects a new instance of type {@code implementation}.
*/
<T> T inject(Class<T> implementation);
/**
* Gets an instance of the given dependency which was declared in
* {@link com.opensymphony.xwork2.inject.ContainerBuilder}.
*/
<T> T getInstance(Class<T> type, String name);
/**
* Convenience method. Equivalent to {@code getInstance(type,
* DEFAULT_NAME)}.
*/
<T> T getInstance(Class<T> type);
/**
* Gets a set of all registered names for the given type
* @param type The instance type
* @return A set of registered names
*/
Set<String> getInstanceNames(Class<?> type);
/**
* Sets the scope strategy for the current thread.
*/
void setScopeStrategy(Scope.Strategy scopeStrategy);
/**
* Removes the scope strategy for the current thread.
*/
void removeScopeStrategy();
}
在系統(tǒng)啟動的時(shí)候,這個(gè)Container的實(shí)現(xiàn)類就會工作,把XML中定義的內(nèi)容進(jìn)行注入。有興趣的讀者可以繼續(xù)探尋這個(gè)接口的實(shí)現(xiàn)類:com.opensymphony.xwork2.inject.ContainerImpl。
靜態(tài)變量(Constant)的注入
@Inject這個(gè)Annotation不僅能夠?qū)涌诘膶?shí)現(xiàn)類進(jìn)行注入,也能夠?qū)o態(tài)變量進(jìn)行注入。
有關(guān)靜態(tài)變量的聲明和注入,在我的另外一篇專欄文章中已經(jīng)詳細(xì)闡述:《深入plugin》 ——
http://www.javaeye.com/wiki/struts2/1333-deep-into-plugin。在這里,我也就不詳細(xì)介紹了。
package節(jié)點(diǎn)詳解
package節(jié)點(diǎn)是整個(gè)配置的核心部分。每個(gè)package,從語義上講,其實(shí)代表了每一個(gè)獨(dú)立的模塊。在這個(gè)模塊中,你可以定義隸屬于這個(gè)模塊的行為方式,而與其他的模塊沒有關(guān)系。所以,每個(gè)package都有獨(dú)立的interceptor、result-type和action的定義,絕大多數(shù)的Runtime配置定義都是通過package節(jié)點(diǎn)實(shí)現(xiàn)的。接下來我們就來詳細(xì)討論一下package中的屬性和子節(jié)點(diǎn)。
基本屬性
1. name
name屬性為每個(gè)package設(shè)置一個(gè)唯一的標(biāo)識,這個(gè)標(biāo)識在所有的package定義中不能重復(fù)。
2. abstract
標(biāo)識這個(gè)package的定義是一個(gè)抽象定義,也就是允許他僅包含聲明式的定義,而不需要在package定義中包含action的定義。
3. extends
通過使用extends,你可以指定本package繼承另外一個(gè)package的所有的配置。當(dāng)某個(gè)package繼承了另外一個(gè)package的所有配置,那么你就無需對父package中已經(jīng)聲明過的配置定義做再次的定義。
同時(shí),如果重復(fù)定義父package中已聲明過的配置定義,那么這些重復(fù)定義聲明將覆蓋父package中的相關(guān)定義。
4. namespace
Struts2 Reference 寫道
The namespace attribute subdivides action configurations into logical modules, each with its own identifying prefix. Namespaces avoid conflicts between action names. Each namespace can have its own "menu" or "help" action, each with its own implementation.
這段來自Struts2的Reference的引用,基本上闡明了namespace的作用:對于action配置進(jìn)行邏輯劃分。
如果我們不為package節(jié)點(diǎn)指定namespace,Struts2默認(rèn)使用一個(gè)空字符串作為默認(rèn)的namespace。當(dāng)然,也可以使用"/"等字符串來表示namespace。
Struts2在根據(jù)URL進(jìn)行尋址的時(shí)候,使用以下的步驟:
1) 根據(jù)URL進(jìn)行Namespace和ActionName的計(jì)算
2) 根據(jù)計(jì)算的得到的Namespace和ActionName查找package節(jié)點(diǎn)中相應(yīng)配置
3) 如果查找失敗,則查找Namespace為空,ActionName為整個(gè)URL的配置
有關(guān)上述3點(diǎn)的詳細(xì)信息,請參考Struts2的Reference:
http://struts.apache.org/2.0.14/docs/namespace-configuration.htmlresult-types節(jié)點(diǎn)
在result-types節(jié)點(diǎn)中,我們可以聲明在本package中所支持的Result類型。這些Result類型,將在action節(jié)點(diǎn)中被引用到。
interceptors節(jié)點(diǎn)
在interceptors節(jié)點(diǎn)中有兩類節(jié)點(diǎn):<interceptor>和<interceptor-stack>。這兩個(gè)節(jié)點(diǎn)都用于聲明攔截器。前者的作用,是真正定義一個(gè)攔截器。而后者則通過引用已經(jīng)定義的攔截器,指定他們的執(zhí)行順序。
當(dāng)我們在試圖在Action中引用攔截器時(shí),我們實(shí)際上是為某個(gè)Action指定需要執(zhí)行哪些攔截器,并且為這些攔截器指定執(zhí)行順序。所以Action所引用的,是某個(gè)<interceptor-stack>中的定義。
缺省配置指向
為了簡化配置,我們可以在package節(jié)點(diǎn)中指定本package內(nèi)的缺省配置指向。這可以通過<default-interceptor-ref>、<default-action-ref>、<global-results>等子節(jié)點(diǎn)來完成。
action節(jié)點(diǎn)
action節(jié)點(diǎn)是所有的Runtime配置的核心內(nèi)容。它的主要作用就是指定URL與Action之間的映射關(guān)系。同時(shí),在action節(jié)點(diǎn)中你也可以指定action執(zhí)行時(shí)的相關(guān)配置,例如action所引用的interceptor等。
參考文檔
上面所有的內(nèi)容,實(shí)際上我只是做了一些簡單的概括和歸納,至于每個(gè)節(jié)點(diǎn)語義和每個(gè)節(jié)點(diǎn)中具體屬性的使用,我認(rèn)為還是需要參考Struts2的Reference,因?yàn)镽eference的講解比任何教程都來的詳細(xì)和正確,所以希望大家在了解了這些配置的基本分類之后,重新閱讀Struts2的Reference的相關(guān)章節(jié),從而更加深刻的理解Struts2配置文件的方方面面:
http://struts.apache.org/2.0.14/docs/configuration-elements.html3 樓
downpour 2009-01-19 12:49
引用to kyo100900:
我在實(shí)際開發(fā)中,會根據(jù)實(shí)際情況編寫一個(gè)相對全的interceptor-stack,作為默認(rèn)的攔截器指向。除非某個(gè)action的攔截器行為非常不同,否則我不會指定額外的interceptor-stack。
在Struts2的官方文檔中,有一篇有關(guān)Performance Tuning的文章:
http://struts.apache.org/2.0.14/docs/performance-tuning.html。在這篇文章中,提到了一個(gè)方面:
Struts2 Reference 寫道
Do not use interceptors you do not need.
不過從整個(gè)Struts2的執(zhí)行過程來看,多執(zhí)行幾個(gè)interceptor,對Struts2本身的性能影響并不是很大,僅僅是ActionInvocation中多執(zhí)行幾句代碼而已。這點(diǎn)代碼的執(zhí)行速度幾乎是可以忽略不計(jì)的。如果是你自己寫的Interceptor,那么你還可以在你的Interceptor中做一定的邏輯判斷來避免無用代碼的執(zhí)行。
所以你所說的2種方法我認(rèn)為都可以,根據(jù)具體的項(xiàng)目情況和個(gè)人喜好而定吧。
2 樓
kyo100900 2009-01-19 12:32
引用想問downpour一個(gè)實(shí)踐問題:
你在開發(fā)的時(shí)候,會將各種 interceptor-stack 區(qū)分開嗎? 就像下面
Xml代碼
<!--Basic stack-->
<interceptor-stack name="basicStack">
<interceptor-ref name="exception"/>
<interceptor-ref name="servletConfig"/>
<interceptor-ref name="prepare"/>
<interceptor-ref name="checkbox"/>
<interceptor-ref name="params"/>
<interceptor-ref name="conversionError"/>
</interceptor-stack>
<interceptor-stack name="defaultStack">
<interceptor-ref name="exception"/>
<interceptor-ref name="alias"/>
.....
</interceptor-stack>
<interceptor-stack name="uploadStack">
<interceptor-ref name="exception"/>
<interceptor-ref name="alias"/>
.....
</interceptor-stack>
....還有其它static
用的時(shí)候,具體的在<action />節(jié)點(diǎn)中指定。
或者就用那個(gè)大而全的默認(rèn) <interceptor-stack name="defaultStack"> 呢?
這個(gè)問題圈子里面討論過,我目前用的是后者,可能是項(xiàng)目不算特別復(fù)雜,沒有遇到所謂的struts性能問題, 你有什么看法呢?