本篇文章讓我們來詳細探討一下Struts2的配置文件的結構、配置文件的各個節點和每個節點中元素的使用方式。
目 錄
[ - ]總攬模塊化管理配置文件簡單的IoCpackage節點詳解參考文檔總攬
Struts2的配置文件是以XML的形式出現的。不過它的XML的語義比較簡單,下面是我抽取了位于struts2-core-2.0.14.jar內部的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節點的定義 -->
<!-- Only have static injections -->
<bean class="com.opensymphony.xwork2.ObjectFactory" static="true" />
<bean class="com.opensymphony.xwork2.util.XWorkConverter" static="true" />
<!-- 省略了其他的靜態注入的定義 -->
<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節點的定義 -->
</interceptors>
<default-interceptor-ref name="defaultStack"/>
</package>
</struts>
在這個配置文件中,我們可以看到,Struts2的XML自身所支持的節點和子節點并不是很多,大致來說,這些節點可以分成基本配置定義和Runtime配置定義。
基本配置定義
基本配置定義,主要是針對在Struts2內部所使用的各種元素的聲明。這些聲明往往規定了Struts2內部的一些行為特征。
例如,配置文件中的<bean>節點,被用于定義Struts2中所使用的接口和實現類,通過Struts2內部實現的IoC,你就可以在不同的實現類之間進行切換。
再例如,配置文件中的<result-type>節點和<interceptor>節點。他們用于定義Struts2中所支持的所有的Result類型和攔截器,這些定義和聲明,將在Runtime的配置定義中被引用。
我之所以把配置文件中的這些節點單獨列出來,作為一個種類,是因為這些節點是不可省略的,也是無法簡化的。所以,如果我們試圖在Struts2中簡化配置,我們就需要在Runtime配置定義中下功夫,而這些基本配置定義,我們可以認為是Runtime配置定義的基礎。
Runtime配置定義
Runtime配置定義,主要指的的是對Struts2運行過程中,具體的某個Action的行為的指定。這些指定主要通過<package>節點中的<action>節點來完成。
仔細翻閱<action>節點,我們可以發現,它是URL與Action之間溝通的橋梁,也就是說,它定義了URL與Action之間的對應關系。同時,它還指定了Action在執行過程中的具體行為,包括Action執行的時候使用什么樣的攔截器、Action執行完畢后,轉向到什么樣的Result等等。
Runtime配置定義是可以簡化的,Struts2中提供了很多種簡化配置的方式,這個在之后的文章中會詳細提到。
模塊化管理配置文件
一旦項目變得很大,項目中同時也并不采取什么簡化配置的措施,那么在默認情況下,配置文件就會變得很大而不易于維護。這個時候,對于配置文件的模塊化管理的需求就顯現出來。Struts2提供了兩種方式對配置文件進行模塊化管理。
plugin機制
Struts2有plugin的機制,有關plugin的具體的知識,請參考我的另外一篇專欄文章:《深入plugin》 ——
http://www.javaeye.com/wiki/struts2/1333-deep-into-plugin。在這里,我也就不詳細介紹了。
在每個plugin中,都會有一個叫做struts-plugin.xml的配置文件,這個配置文件的格式與struts-default.xml的格式是相同的。可以在其中做出任何的Struts2的定義和配置。我們知道,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中的配置的效果實際上與struts-default.xml的效果是相同的。這樣,通過各種各樣不同的plugin,就等于將Struts2的配置,按照plugin的功能不同而分開了。從而起到了配置文件模塊化管理的效果。
使用include節點
plugin中的配置文件,實際上是位于classpath的JAR包中的,那么我們在項目中,如何對一個龐大的配置文件進行拆分呢?在Struts2中,可以使用include節點對所有的Struts2配置文件進行拆分和模塊化管理。例如:
Xml代碼
<struts>
<include file="struts-default.xml"/>
<include file="web/struts-config.xml"/>
<include file="web/struts-action.xml"/>
</struts>
其中,file所指定的文件是相對于classpath的相對目錄中的文件。而每個配置文件的格式與struts-default.xml的格式也是相同的。
通過include節點,我們就可以對一個比較大的配置文件按照功能和模塊進行拆分,這在一個大型的團隊開發中,是相當有意義的。
簡單的IoC
在基本配置定義中,有兩個很常用的節點:<bean>和<constant>。在系統啟動的時候,Struts2會根據配置文件中這些<bean>和<constant>節點的定義進行加載,并初始化成為Struts2的默認行為。這種初始化的行為,非常類似于Spring中的依賴注入(IoC),從而使得你不再需要擔心這些對象在運行時的創建和銷毀,所有的工作都由Struts2內部的機制實現。接下來我們就來看看Struts2是如何實現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的實現使用了Annotation的方式進行,而整個依賴注入的實現,是通過一個內部的容器類進行的。Struts2的依賴注入,與Guice的機制完全一致。根據注入的內容的不同,Struts2的IoC可以對容器中的對象的依賴關系進行管理,也可以注入一些靜態變量。
bean注入
對于bean的注入,對應于XML中的bean的節點聲明。我把其中的機制分成了3個部分:
1. 容器中對象的聲明
Xml代碼
<bean class="com.opensymphony.xwork2.ObjectFactory" name="xwork" />
<bean type="com.opensymphony.xwork2.ObjectFactory" name="struts" class="org.apache.struts2.impl.StrutsObjectFactory" />
這點沒什么好說的,在struts.xml中,你可以為某個接口聲明它所對應的實現類。
name屬性
你可以聲明多個實現類,使用name屬性進行區分。在注入的時候,將使用這個屬性的值作為接口實現類的選擇。
required屬性
你還可以通過required屬性,來指定是否在運行時必不可少的注入。如果reqired被設置成false,那么當不存在相應的接口定義時,注入將被忽略。
static屬性
在XML的定義中,還可以使用static屬性。如果static屬性被設置成true,那么注入將針對bean中的static方法和static屬性進行。
2. 在代碼中使用Annotation進行注入
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這樣一個Annotation進行對象依賴注入。在上面的例子中,我們可以看到,@Inject這個Annotation,可以作用在屬性上,也可以作用在方法上,甚至可以作用在方法的參數上。
在默認情況下,如果@Inject不指定value,那么XML配置定義中的name="default"或者name=""的實現類定義將被注入。
那么,在struts-default.xml中,Struts2到底選擇了那些實現類,作為Struts2或者XWork內部接口的默認實現類呢?默認情況下,struts-default.xml中定義的bean的name="struts"的將被作為默認的接口實現類被注入。這些默認行為,是由org.apache.struts2.config.BeanSelectionProvider所決定的,有興趣的讀者可以參閱這個類的源碼。
3. 內部的Container機制完成一切背后工作
上面看到的,是現象。在內部,Struts2通過一個Container來實現所有的注入機制。
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();
}
在系統啟動的時候,這個Container的實現類就會工作,把XML中定義的內容進行注入。有興趣的讀者可以繼續探尋這個接口的實現類:com.opensymphony.xwork2.inject.ContainerImpl。
靜態變量(Constant)的注入
@Inject這個Annotation不僅能夠對接口的實現類進行注入,也能夠對靜態變量進行注入。
有關靜態變量的聲明和注入,在我的另外一篇專欄文章中已經詳細闡述:《深入plugin》 ——
http://www.javaeye.com/wiki/struts2/1333-deep-into-plugin。在這里,我也就不詳細介紹了。
package節點詳解
package節點是整個配置的核心部分。每個package,從語義上講,其實代表了每一個獨立的模塊。在這個模塊中,你可以定義隸屬于這個模塊的行為方式,而與其他的模塊沒有關系。所以,每個package都有獨立的interceptor、result-type和action的定義,絕大多數的Runtime配置定義都是通過package節點實現的。接下來我們就來詳細討論一下package中的屬性和子節點。
基本屬性
1. name
name屬性為每個package設置一個唯一的標識,這個標識在所有的package定義中不能重復。
2. abstract
標識這個package的定義是一個抽象定義,也就是允許他僅包含聲明式的定義,而不需要在package定義中包含action的定義。
3. extends
通過使用extends,你可以指定本package繼承另外一個package的所有的配置。當某個package繼承了另外一個package的所有配置,那么你就無需對父package中已經聲明過的配置定義做再次的定義。
同時,如果重復定義父package中已聲明過的配置定義,那么這些重復定義聲明將覆蓋父package中的相關定義。
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配置進行邏輯劃分。
如果我們不為package節點指定namespace,Struts2默認使用一個空字符串作為默認的namespace。當然,也可以使用"/"等字符串來表示namespace。
Struts2在根據URL進行尋址的時候,使用以下的步驟:
1) 根據URL進行Namespace和ActionName的計算
2) 根據計算的得到的Namespace和ActionName查找package節點中相應配置
3) 如果查找失敗,則查找Namespace為空,ActionName為整個URL的配置
有關上述3點的詳細信息,請參考Struts2的Reference:
http://struts.apache.org/2.0.14/docs/namespace-configuration.htmlresult-types節點
在result-types節點中,我們可以聲明在本package中所支持的Result類型。這些Result類型,將在action節點中被引用到。
interceptors節點
在interceptors節點中有兩類節點:<interceptor>和<interceptor-stack>。這兩個節點都用于聲明攔截器。前者的作用,是真正定義一個攔截器。而后者則通過引用已經定義的攔截器,指定他們的執行順序。
當我們在試圖在Action中引用攔截器時,我們實際上是為某個Action指定需要執行哪些攔截器,并且為這些攔截器指定執行順序。所以Action所引用的,是某個<interceptor-stack>中的定義。
缺省配置指向
為了簡化配置,我們可以在package節點中指定本package內的缺省配置指向。這可以通過<default-interceptor-ref>、<default-action-ref>、<global-results>等子節點來完成。
action節點
action節點是所有的Runtime配置的核心內容。它的主要作用就是指定URL與Action之間的映射關系。同時,在action節點中你也可以指定action執行時的相關配置,例如action所引用的interceptor等。
參考文檔
上面所有的內容,實際上我只是做了一些簡單的概括和歸納,至于每個節點語義和每個節點中具體屬性的使用,我認為還是需要參考Struts2的Reference,因為Reference的講解比任何教程都來的詳細和正確,所以希望大家在了解了這些配置的基本分類之后,重新閱讀Struts2的Reference的相關章節,從而更加深刻的理解Struts2配置文件的方方面面:
http://struts.apache.org/2.0.14/docs/configuration-elements.html3 樓
downpour 2009-01-19 12:49
引用to kyo100900:
我在實際開發中,會根據實際情況編寫一個相對全的interceptor-stack,作為默認的攔截器指向。除非某個action的攔截器行為非常不同,否則我不會指定額外的interceptor-stack。
在Struts2的官方文檔中,有一篇有關Performance Tuning的文章:
http://struts.apache.org/2.0.14/docs/performance-tuning.html。在這篇文章中,提到了一個方面:
Struts2 Reference 寫道
Do not use interceptors you do not need.
不過從整個Struts2的執行過程來看,多執行幾個interceptor,對Struts2本身的性能影響并不是很大,僅僅是ActionInvocation中多執行幾句代碼而已。這點代碼的執行速度幾乎是可以忽略不計的。如果是你自己寫的Interceptor,那么你還可以在你的Interceptor中做一定的邏輯判斷來避免無用代碼的執行。
所以你所說的2種方法我認為都可以,根據具體的項目情況和個人喜好而定吧。
2 樓
kyo100900 2009-01-19 12:32
引用想問downpour一個實踐問題:
你在開發的時候,會將各種 interceptor-stack 區分開嗎? 就像下面
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
用的時候,具體的在<action />節點中指定。
或者就用那個大而全的默認 <interceptor-stack name="defaultStack"> 呢?
這個問題圈子里面討論過,我目前用的是后者,可能是項目不算特別復雜,沒有遇到所謂的struts性能問題, 你有什么看法呢?