最近幫人做了個demo,用到了Hibernate, Spring, Struts的結構,對Struts也看了些內(nèi)容,看到這篇文章覺得應該對使用Struts的朋友有所益處,所以翻譯過來,共同學習。
關于Hibernate+Spring+Struts的架構,Spring自帶的sample:Jpetstore是一個參考。
原文鏈接:
http://www.onjava.com/lpt/a/5411擴展Struts
簡介:
我看到很多項目中,開發(fā)者實現(xiàn)了自己的MVC框架,并不是因為他們想做同Struts根本不同的東西,而是因為他們并沒有意識到如何擴展Struts。開發(fā)自己的MVC框架可以獲得全部的控制權,但是這也意味著需要很多資源來實現(xiàn)它(人力物力),在緊張的日程安排下,有時候這是不可能的。
Struts不僅僅是一個強大的框架,同時它也是可擴展的。你可以以三種方式來擴展Struts。
1, PlugIn:如果你想在application startup或shutdown的時候做一些業(yè)務邏輯的話,那就創(chuàng)建你自己的PlugIn類。
2, RequestProcessor:如果你想在請求被處理的過程中某個時刻做一些業(yè)務邏輯的話,那么創(chuàng)建你自己的RequestProcessor類。比如說,在每次請求執(zhí)行之前,你可以擴展RequestProcessor來檢查用戶是否登陸了以及他是否有權限去執(zhí)行某個特定的action。
3, ActionServlet:如果你想在application startup和shutdown的時候以及請求被處理的時候做某些業(yè)務邏輯,你也可以擴張ActionServlet類。不過你應當在PlugIn和RequestProcessor都不能解決你的需求的時候來使用ActionServlet。
在這篇文章中,我們將使用一個Struts應用的示例來示范如何使用這三種方式來擴展Struts。示例程序的代碼可以從http://www.onjava.com/onjava/2004/11/10/examples/sample1.zip下載。兩個擴展Struts成功的范例是Struts自身的Validation和Tiles框架。
我們假設你已經(jīng)比較熟悉Struts框架并且知道如何使用它創(chuàng)建一個簡單的應用。如果你想知道更多關于Struts的內(nèi)容,請參考官方主頁。
PlugIn
PlugIn是一個接口,你可以創(chuàng)建一個實現(xiàn)該接口的類,當application startup或shutdown的時候做些事情。
比方說,我創(chuàng)建了一個使用Hibernate作為持久層的web應用,我想當應用啟動的時候就初始化Hibernate,這樣子當我的web應用受到第一個請求的時候,Hibernate就已經(jīng)是配置好的并且可用的。同時我們想當application關閉的時候關閉Hibernate。我們可以用一個Hibernate PlugIn來實現(xiàn)這個需求,通過如下的兩步:
1, 創(chuàng)建一個類實現(xiàn)了PlugIn接口:
public class HibernatePlugIn implements PlugIn{
? ?private String configFile;
? ?// This method will be called at application shutdown time
? ?public void destroy() {
? ?? ?System.out.println("Entering HibernatePlugIn.destroy()");
? ?? ?//Put hibernate cleanup code here
? ?? ?System.out.println("Exiting HibernatePlugIn.destroy()");
? ?}
? ?//This method will be called at application startup time
? ?public void init(ActionServlet actionServlet, ModuleConfig config)
? ?? ?throws ServletException {
? ?? ?System.out.println("Entering HibernatePlugIn.init()");
? ?? ?System.out.println("Value of init parameter " +
? ?? ?? ? ? ? ? ? ? ? ? ? getConfigFile());
? ?? ?System.out.println("Exiting HibernatePlugIn.init()");
? ?}
? ?public String getConfigFile() {
? ?? ?return name;
? ?}
? ?public void setConfigFile(String string) {
? ?? ?configFile = string;
? ?}
}
實現(xiàn)PlugIn接口的類必須完成兩個方法:init()和destroy()。當application startup的時候init()方法被調用,當shutdown的時候destroy()方法被調用。Struts還允許給你的PlugIn類傳遞初始化參數(shù)。為了傳遞參數(shù),你必須在PlugIn類中為每一個參數(shù)創(chuàng)建JavaBean式的setter方法。在我們的HibernatePlugIn類中,我會把configFile的name作為參數(shù)傳進去,而不是硬編碼到程序中。
2, 在struts-config.xml中添加如下的代碼來通告Struts有新的PlugIn:
<struts-config>
? ?...
? ?<!-- Message Resources -->
? ?<message-resources parameter=
? ? ? ? ? "sample1.resources.ApplicationResources"/>
? ?<!-- Declare your plugins -->
? ?<plug-in className="com.sample.util.HibernatePlugIn">
? ?? ?<set-property property="configFile"
? ?? ?? ?value="/hibernate.cfg.xml"/>
? ?</plug-in>
</struts-config>
屬性className是實現(xiàn)了PlugIn接口的類的全限定名。對于每一個初始化參數(shù),可以使用<set-property>元素傳遞參數(shù)。在我們的例子中,我要把config文件的名字傳進去,所以使用了一個帶有配置文件路徑的<set-property>。
Struts的Tiles和Validator框架都使用PlugIn來讀取配置文件進行初始化。另外兩件PlugIn可以幫你做到的事情是:
l 如果你的application依賴于某些配置文件,那么你可以在PlugIn類中檢查它們是否可用,如果不可用的話拋出一個ServletException,這樣就可以使ActionServlet變?yōu)椴豢捎谩?br />l PlugIn接口的init()方法是你可以改變ModuleConfig的最后機會,ModuleConfig是一組靜態(tài)配置信息的集合,用來描述基于Struts模塊。Struts將會在所有PlugIn處理完后釋放ModuleConfig。
Request是如何被處理的
ActionServlet是Struts框架中唯一的Servlet,它負責處理所有request。無論何時接收到一個request,它都會先嘗試為當前的request尋找一個sub-application。一旦一個sub-application被找到,ActionServlet就會為那個sub-application創(chuàng)建一個RequestProcessor對象,調用這個對象的process()方法并把HttpServletRequest和HttpServletResponse對象傳入。
RequestProcessor.process()就是大部分request被處理的地方。process()方法使用了Template Method模式實現(xiàn),其中有很多獨立的方法來執(zhí)行請求處理的每一步驟,這些方法將會在process方法中依次被調用。比如,將會有一個獨立的方法用來尋找當前request對應的ActionForm類,一個方法來檢查當前用戶是否有執(zhí)行action mapping所必須的權限。這些給與我們極大的靈活性。在發(fā)布的Struts包中有一個RequestProcessor類提供了請求處理每一步驟的默認實現(xiàn)。這就意味著你可以僅僅重寫你感興趣的方法,其它的使用默認的實現(xiàn)。舉例來說,默認地Struts調用request.isUserInRole()來檢查用戶是否有權限執(zhí)行當前的ActionMapping;這時如果你想通過查詢數(shù)據(jù)庫來實現(xiàn),你所要做的就是重寫processRoles()方法,通過查詢出的用戶是否擁有必須的權限來返回true或false。
首先我們將會看到缺省情況下,process()方法是如何實現(xiàn)的,然后我將會詳細解釋默認的RequestProcessor類中的每一個方法,這樣你就可以決定哪一部分是你想要改變的。
public void process(HttpServletRequest request,HttpServletResponse response)
? ? throws IOException, ServletException {
? ? ? ? // Wrap multipart requests with a special wrapper
? ? ? ? request = processMultipart(request);
? ? ? ? // Identify the path component we will
? ? ? ? // use to select a mapping
? ? ? ? String path = processPath(request, response);
? ? ? ? if (path == null) {
? ? ? ? ? ? return;
? ? ? ? }
? ? ? ? if (log.isDebugEnabled()) {
? ? ? ? ? ? log.debug("Processing a '" + request.getMethod() +
? ? ? ? ? ? ? ? ? ? ? "' for path '" + path + "'");
? ? ? ? }
? ? ? ? // Select a Locale for the current user if requested
? ? ? ? processLocale(request, response);
? ? ? ? // Set the content type and no-caching headers
? ? ? ? // if requested
? ? ? ? processContent(request, response);
? ? ? ? processNoCache(request, response);
? ? ? ? // General purpose preprocessing hook
? ? ? ? if (!processPreprocess(request, response)) {
? ? ? ? ? ? return;
? ? ? ?}
? ? ? ? // Identify the mapping for this request
? ? ? ? ActionMapping mapping =
? ? ? ? ? ? processMapping(request, response, path);
? ? ? ? if (mapping == null) {
? ? ? ? ? ? return;
? ? ? ? }
? ? ? ? // Check for any role required to perform this action
? ? ? ? if (!processRoles(request, response, mapping)) {
? ? ? ? ? ? return;
? ? ? ? }
? ? ? ? // Process any ActionForm bean related to this request
? ? ? ? ActionForm form =
? ? ? ? ? ? processActionForm(request, response, mapping);
? ? ? ? processPopulate(request, response, form, mapping);
? ? ? ? if (!processValidate(request, response, form, mapping)) {
? ? ? ? ? ? return;
? ? ? ? }
? ? ? ? // Process a forward or include specified by this mapping
? ? ? ? if (!processForward(request, response, mapping)) {
? ? ? ? ? ? return;
? ? ? ? }
? ? ? ? if (!processInclude(request, response, mapping)) {
? ? ? ? ? ? return;
? ? ? ? }
? ? ? ? // Create or acquire the Action instance to
? ? ? ? // process this request
? ? ? ? Action action =
? ? ? ? ? ? processActionCreate(request, response, mapping);
? ? ? ? if (action == null) {
? ? ? ? ? ? return;
? ? ? ? }
? ? ? ? // Call the Action instance itself
? ? ? ? ActionForward forward =
? ? ? ? ? ? processActionPerform(request, response,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? action, form, mapping);
? ? ? ? // Process the returned ActionForward instance
? ? ? ? processForwardConfig(request, response, forward);
? ? }
1, processMutipart():在這個方法中,Struts將會讀取request來檢查request的contentType是否是multipart/form-data。如果是的話,將會解析request并且將之包裝到HttpServletRequest中。當你創(chuàng)建了一個HTML FORM用來提交數(shù)據(jù),那么request的contentType默認就是application/x-www-form-urlencoded。但是如果你的form使用了file類型的input控件允許用戶上傳文件的話,你就必須將contentType改為multipart/form-data。如果是這樣的情況,你就不能再通過getParameter()來獲取用戶提交的數(shù)據(jù);你必須將request作為一個InputStream來讀取,并且自己解析它來獲得參數(shù)值。
2, processPath():在這個方法中,Struts將會讀取request的URI,來確定路徑元素,這個元素是用來獲取ActionMappint元素。
3, processLocale():在這個方法中,Struts將會為當前request取得Locale,如果配置過的話,還可以將這個對象作為HttpSession中org.apache.struts.action.LOCALE屬性的值而保存。作為這個方法的副作用,HttpSession將會被創(chuàng)建,如果你不想創(chuàng)建的話,你可以在ControllerConfig中將locale屬性設為false,在struts-config.xml中象如下這樣:
<controller>
? ?<set-property? property="locale" value="false"/>
</controller>
4, processContent():通過調用response.setContentType()來為response設置contentType。這個方法首先會嘗試從struts-config.xml配置中得到contentType。缺省情況下使用text/html。如果想覆蓋它,可以象如下這樣:
<controller>
? ?<set-property? property="contentType" value="text/plain"/>
</controller>
5, processNoCache():如果配置是no-cache,Struts將會為每個response設置下面三個headers:
requested in struts config.xml
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 1);
如果你想設置no-cache header,在struts-config.xml中加入下面信息:
<controller>
? ?<set-property? property="noCache" value="true"/>
</controller>
6, processPreprocess():這個方法為預處理提供一個hook,可以在子類中覆蓋它。它的缺省實現(xiàn)沒有作任何事情,總是返回true。返回false的話將會終止當前請求的處理。
7, processMapping():這個方法將會用路徑信息得到一個ActionMapping對象。也就是struts-config.xml文件中的<action>元素:
<action path="/newcontact" type="com.sample.NewContactAction"
? ? ? ? name="newContactForm" scope="request">
? ?<forward name="sucess" path="/sucessPage.do"/>
? ?<forward name="failure" path="/failurePage.do"/>
</action>
ActionMapping元素包含了Action類的名稱和處理請求使用的ActionForm等等信息。它還包含當前ActionMapping配置的ActionForwards信息。
8, processRoles():Struts web應用提供了一個授權方案。也就是說,一旦一個用戶登入了容器,struts的processRoles()方法將會通過調用request.isUserInRole(),來檢查他是否有必須的角色來運行一個給定的ActionMapping。
<action path="/addUser" roles="administrator"/>
假設你有一個AddUserAction并且你只想讓administrator能夠增加新的user。你所要做的就是給你的AddUserAction元素增加一個role屬性,這個屬性的值為administrator。這樣,在運行AddUserAction之前,這個方法會確保用戶擁有administraotr的角色。
9, processActionForm():每一個ActionMapping都一個相應的ActionForm類。當Struts處理一個ActionMapping的時候,它將會從<action>元素的name屬性中找出對應的ActionForm類的名稱。
<form-bean name="newContactForm"
? ? ? ? ? ?type="org.apache.struts.action.DynaActionForm">
? ?? ?<form-property name="firstName"
? ? ? ? ? ? ? ? ? ? ? ? ? type="java.lang.String"/>
? ?? ?<form-property name="lastName"
? ? ? ? ? ? ? ? ? ? ? ? ? type="java.lang.String"/>
</form-bean>
在我們的例子中,它會先在request scope中檢查是否有一個org.apache.struts.action.DynaActionForm類的對象存在。如果有它將會使用這個對象,如果沒有它將會創(chuàng)建一個新的對象并把它設置在request scope。
10, processPopulate():在這個方法中,Struts將會用相匹配的request參數(shù)裝配ActionForm的實例變量。
11, processValidate():Struts將會調用你的ActionForm類的validate方法。如果你從validate()返回ActionErrors,它將會將user重定向到<action>元素的input屬性指定的頁面。
12, processForward()和processInclude():在這些方法中,Struts將會檢查<action>元素的forward或include屬性,如果找到了,將會把forward或include請求放置到配置的頁面中。
<action forward="/Login.jsp" path="/loginInput"/>
<action include="/Login.jsp" path="/loginInput"/>
你可以從這些方法的名字上猜測它們的不同:processForward()最終調用RequestDispatcher.forward(),而processInclude()調用RequestDispatcher.include()。如果你同時配置了forward和include屬性,它將會總是調用forward,因為forward先被處理。
13, processActionCreate():這個方法從<action>元素的type屬性中獲取獲得Action類的名字并且創(chuàng)建返回它的實例。在我們的例子中,它將會創(chuàng)建一個com.sample.NewContactAction類的實例。
14, processActionPerform():這個方法調用你的Action類的excute()方法,你的業(yè)務邏輯也就是在excute方法中。
15, processForwardConfig():你的Action類的excute()方法將會返回一個ActionForward對象,這個對象將指出哪個頁面是顯示給用戶的頁面。因此,Struts將會為那個頁面創(chuàng)建一個RequestDispatcher,并且調用RequestDispatcher.forward()。
上面的列表說明了默認的RequestProcessor實現(xiàn)在處理請求時每一步作的工作,以及執(zhí)行的順序。正如你所看到的,RequestProcessor是非常靈活的,允許你通過設置<controller>元素的屬性來配置它。舉例來說,如果你的應用準備生成XML內(nèi)容來代替HTML,你就可以通過設置controller元素的屬性來通知Struts這些情況。
創(chuàng)建你自己的RequestProcessor
通過上面,我們了解到了RequestProcessor的默認實現(xiàn)是如何工作的。現(xiàn)在我們要演示一個例子來說明如何定制你自己的RequestProcessor。為了展示創(chuàng)建用戶定制的RequestProcessor,我們將會讓我們的示例實現(xiàn)下面兩個業(yè)務需求:
l 我們想創(chuàng)建一個ContactImageAction類,它將生成圖片而不是平常的HTML頁面。
l 在每個請求處理之前,我們都想通過檢查session中的userName屬性來確定用戶是否已經(jīng)登陸。如果那個屬性沒有找到,我們會把用戶重定向到登陸頁面。
我們將分兩步實現(xiàn)這些業(yè)務需求。
1, 創(chuàng)建你的CustomRequestProcessor類,它將繼承自RequestProcessor類,如下:
public class CustomRequestProcessor
? ? extends RequestProcessor {
? ? ? ? protected boolean processPreprocess (
? ? ? ? ? ? HttpServletRequest request,
? ? ? ? ? ? HttpServletResponse response) {
? ? ? ? ? ? HttpSession session = request.getSession(false);
? ? ? ? //If user is trying to access login page
? ? ? ? // then don't check
? ? ? ? if( request.getServletPath().equals("/loginInput.do")
? ? ? ? ? ? || request.getServletPath().equals("/login.do") )
? ? ? ? ? ? return true;
? ? ? ? //Check if userName attribute is there is session.
? ? ? ? //If so, it means user has allready logged in
? ? ? ? if( session != null &&
? ? ? ? session.getAttribute("userName") != null)
? ? ? ? ? ? return true;
? ? ? ? else{
? ? ? ? ? ? try{
? ? ? ? ? ? ? ? //If no redirect user to login Page
? ? ? ? ? ? ? ? request.getRequestDispatcher
? ? ? ? ? ? ? ? ? ? ("/Login.jsp").forward(request,response);
? ? ? ? ? ? }catch(Exception ex){
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? return false;
? ? }
? ? protected void processContent(HttpServletRequest request,
? ? ? ? ? ? ? ? HttpServletResponse response) {
? ? ? ? ? ? //Check if user is requesting ContactImageAction
? ? ? ? ? ? // if yes then set image/gif as content type
? ? ? ? ? ? if( request.getServletPath().equals("/contactimage.do")){
? ? ? ? ? ? ? ? response.setContentType("image/gif");
? ? ? ? ? ? ? ? return;
? ? ? ? ? ? }
? ? ? ? super.processContent(request, response);
? ? }
}
在CustomRequestProcessor類的processPreprocess方法中,我們檢查session的userName屬性,如果沒有找到,就將用戶重定向到登陸頁面。
對于生成圖片作為輸出的需求,我們必須覆蓋processContent方法,首先檢查請求是否是/contactimage路徑。如果是的話,我們就會將contentType設置為image/gif;否則設置為text/html。
2, 在你的struts-config.xml文件的<action-mappint>元素之后加入下面的文字,告訴Struts CustomRequestProcessor應當被用作RequestProcessor類:
<controller>
? ?<set-property? property="processorClass"
? ?value="com.sample.util.CustomRequestProcessor"/>
</controller>
請注意,當你只有很少的action類需要生成非text/html類型的輸出時,你覆寫processContent()方法是OK的。如果不是這樣子的話,你應該創(chuàng)建一個Struts的子應用來處理請求生成圖片的Action,并為它們將contentType設置為image/gif。
Struts的Tiles框架就是使用它自己的RequestProcessor來裝飾Struts的輸出。
ActionServlet
如果你查看你的Struts web應用的web.xml,你會看到這樣的文字:
<web-app >
? ? ? ? <servlet>
? ? ? ? ? ? <servlet-name>action=</servlet-name>
? ? ? ? ? ? <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
? ? ? ? ? ? <!-- All your init-params go here-->
? ? ? ? </servlet>
? ? ? ? <servlet-mapping>
? ? ? ? ? ? <servlet-name>action</servlet-name>
? ? ? ? ? ? <url-pattern>*.do</url-pattern>
? ? ? ? </servlet-mapping>
</web-app >
這意味著ActionServlet負責處理你所有Struts的請求。你可以創(chuàng)建一個ActionServlet的子類,當應用啟動,關閉,每個請求的時候做一些特定的事情。但是在繼承ActionServlet類之前,你應該盡量創(chuàng)建一個PlugIn或RequestProcessor去解決你的問題。在Servlet1.1之前,Tiles框架是基于ActionServlet來修飾生成的響應。但是從1.1之后,它開始使用TilesRequestProcessor類。
總結
決定開發(fā)你自己的MVC框架是一個非常大的決定,你必須要考慮開發(fā)和維護框架代碼所花費的時間和資源。Struts是一個非常強大和穩(wěn)定的框架,你可以修改它來滿足你絕大多數(shù)的業(yè)務需求。
但另一方面,也不要草率地做出擴展Struts的決定。如果你在RequestProcessor中寫了一些性能比較低的代碼,它將會在每次請求時執(zhí)行,因而降低你整個應用的效率。而且還是有一些情況,開發(fā)自己的MVC框架要比擴展Struts好。