Struts2官網:http://struts.apache.org/
目前最新版本:Struts 2.3.24
Struts1已經完全被淘汰了,而Struts2是借鑒了webwork的設計理念而設計的基於MVC的框架。感興趣的可以了解一下webwork概念,這里不做涉及。我們常說的Struts2 Spring Hibernate三大面試中常被問到的框架,其實Struts2和SpringMVC是兩個獨立的MVC框架。而且SpringMVC是目前最好的MVC框架(個人感覺),但是由於舊的一些項目運維需要Struts2框架的知識,故而做整理。
下載了Struts2框架之后,我們要學習什么?1.學習框架運作的整個流程;2.學習如何搭建框架;3.自己動手寫一個。先來看看框架的流程,如下:
Struts2(由於Struts1表現層單一,無法跟Freemarker等技術整合),它采用攔截器的機制來處理用戶的請求。
先來講講Struts2的原理圖,如上圖所示:
1.當用戶發起請求時(一個URL),服務器端的Web容器收到了請求。
2.這時,Struts2的核心控制器FilterDispatcher接受用戶發起的請求,然后判斷這個請求是交給action處理?還是交給web組件來處理?如果請求的action或web組件不存在,則報404錯誤。在整個處理過程中,需要一個輔助對象:Action映射器(ActionMapper),ActionMapper會確定調用哪個Action(這個過程的實現是依靠ActionMapper返回一個收集Action詳細信息的ActionMaping對象)
3.然后,來交給Action來處理,它會根據struts.xml的配置信息(首先執行攔截此action的所有攔截器,然后再執行請求的action對象<在這個處理過程中需要輔助對象:Action代理(ActionProxy);配置管理器(ConfigurationManager);ActionInvocation>,),
4.Action執行完畢之后,返回一個結果(此結果用字符串來表示),這個結果經過攔截Action的所有攔截器之后,返回給主控制器。主控制器根據此結果從配置文件中找到真正的路徑,然后將請求轉發給相應的視圖。
5.由視圖客戶端作出響應。
那么接下來我們講講搭建框架,最好自己親自搭建一遍或者跟着舊的項目把流程走一遍:
首先,將下載好的Struts2中的jar包拷貝到你構建的web project中,根據上圖的設計流程,我們知道Struts2是通過過濾器,將所有的請求過濾,然后分發到各個action(action其實就是一個類,就是一個POJO類),然后根據返回的String字符串,查找Struts2的配置文件,找到應該返回到哪個頁面,即可。具體如下:
拷貝jar包到項目中,
再來配置web.xml:
<filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class> </filter>
<filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
再來配置struts.xml配置文件(struts.xml要在src目錄下)
<struts> <package name="default" extends="struts-default" namespace="/"> </package> </struts>
再來創建Action(它就是一個POJO類)
public class HelloAction{ public String execute(){ System.out.println("Hello Struts2!");
return "success"; } }
再在struts.xml中配置action和返回結果集:
<struts> <package name="default" extends="struts-default" namespace="/"> <action name="hello" class="com.hp.it.HelloAction">
<result name="success">/hello.jsp</result>
</action> </package> </struts>
注意,這里的<action>標簽中的name屬性要與url路徑中的過濾到的string相對應;class屬性要與你寫的action類相對應(注意要有包名).<result>標簽(result是結果集)中的那么屬性要與action中返回的string字符串相對應,返回的路徑是WEB-INF/下的hello.jsp,即http://localhost:8080/projectname/hello.action
再寫前台的hello.jsp:
<html> <head></head> <body><h1>Hello</h1></body> </html>
整個過程就是這樣的,那么問題來了:當有多個請求的時候,要寫很多execute()方法么?excute方法是默認的哦!
當然不是的,我們注意到,在struts.xml的配置文件中,標簽<action>中除了name class 屬性,還有一個method屬性,很顯然,這個標簽可以指定我們映射到的方法。這個當然就是幾個簡單的擴張了。相應的struts.xml的配置文件:
<package name="default" extends="struts-default" namespace="/"> <action name="addUser" class="com.hp.it.UserAction" method="addUser"> <result name="success">/WEB-INF/user/addUser.jsp</result> </action>
<action name="updateUser" class="com.hp.it.UserAction" method="updateUser">
<result name="success">/WEB-INF/user/updateUser.jsp</result>
</action>
</package>
相應的UserAction中如下:
public class UserAction{ public String addUser(){ System.out.println("addUser"); return "success"; } public String updateUser(){ System.out.println("updateUser"); return "success"; } }
那么問題又來了,一個方法寫一個action,那么這樣會導致配置文件中的action量很大。一種解決辦法是可以在<package>標簽的平行目錄下,增加標簽如下所示:通過增加<include>標簽來導入其他的xml文件。
<struts> <package name="default" extends="struts-default" namespace="/"> <action name="hello" class="com.hp.it.HelloAction"> <result name="success">/hello.jsp</result> </action> </package> <include file="otherStruts.xml"> </struts>
引入其他的xml文件,固然可以,但是依然無法解決action配置文件過多的問題,在整理Struts2提供了兩種解決方案,如下:
第一種URL:
http://localhost:8080/projectname/User!add http://localhost:8080/projectname/User!update http://localhost:8080/projectname/User!list
如上三個URL所示,User是Action類名,對應UserAction類:后面的!+方法名。
第二種URL:
http://localhost:8080/projectname/User?method:add http://localhost:8080/projectname/User?method:update http://localhost:8080/projectname/User?method:list
如上三個URL所示,User是Action類名,對應UserAction類:后面的?+method:方法名。
再接着,我們看看我們的struts.xml該如何來寫?
<package name="default" extends="struts-default" namespace="/"> <action name="user" class="com.hp.it.UserAction" > <result name="add">/WEB-INF/user/addUser.jsp</result> </action> <action name="user" class="com.hp.it.UserAction" > <result name="update">/WEB-INF/user/updateUser.jsp</result> </action> <action name="user" class="com.hp.it.UserAction" > <result name="list">/WEB-INF/user/listUser.jsp</result> </action> </package>
注意了啊,這里的action屬性name是url中那個user,即是對應着UserAction.方法是由調用的時候來決定的看具體使用誰。
同樣的,我們來看看在UserAction中應該這樣來寫:
public class UserAction{ public String addUser(){ System.out.println("addUser"); return "add"; } public String updateUser(){ System.out.println("updateUser"); return "update"; } public String listUser(){ System.out.println("listUser"); return "list"; } }
這個方法雖然減少了action的配置,但是增加了大量的結果集的配置。所有問題來了,有沒有解決這個問題的方法呢?有
我們可以通過通配符來解決這個問題,這兒有一個核心思想:(約定優於配置),如下:
<action name="*_*" class="com.hp.it.action.{1}Action" method="{2}"> <result>/WEB-INF/{1}/{2}.jsp</result> </action>
這里要強調一下,標簽<result>默認的屬性是 name="success"。約定優於配置,那么我們的約定是對於URl來說,它的格式應該如下面這樣來向服務器端發出請求:
http://localhost:8080/projectname/User_add http://localhost:8080/projectname/User_update http://localhost:8080/projectname/User_list
上面這種對於URL的約定,直接可以使用通配符*_*來對它過濾。大大簡化了配置文件的大小。(這種情況下,注意大小寫字母)
前面這些都是在說,服務器端的跳轉,那么客戶端的跳轉怎么來實現呢?比如說,我們的User類在add完成之后,往往要跳轉到它的list頁面,這時候應該這樣來配置:
<action name="*_*" class="com.hp.it.action.{1}Action" method="{2}">
<result>/WEB-INF/{1}/{2}.jsp</result>
<result type="redirect" name="r_list">/{1}_list.action</result>
</action>
大概看這個的含義就是說,當name=r_list的時候,進行重定向,並且重定向到{1}_list.action。相應的UserAction應該這么寫
public class UserAction{ public String addUser(){ System.out.println("addUser"); return "r_list"; } public String updateUser(){ System.out.println("updateUser"); return "update"; } public String listUser(){ System.out.println("listUser"); return "list"; } }
如果按上述方法來做,是不是效果會更好呢。但是我們通常看到的URL,往往很少再其屁股后面加".action"這個后綴,其實這個是可以
上面這個配置語句配置了對於.action的請求都進行過濾,同樣也可以我們自己設定,如下:
<constant name="struts.action.extension" value="action,do,zxg" />
如上這種,當以.action;.do;.zxg的URL路徑訪問的時候,都會進入filter來過濾的。
*************************************************************************************************************************
接下來,我們再看看Struts2中是如何對參數傳值做處理的(了解地址和類的對應關系;了解數據的通信(參數)的)。這部分是很關鍵的,而且一定要掌握清楚,不要跟SprigMVC相混淆。這段邏輯如果錯誤的話,調試代碼的時候,介於前端和后端之間,斷點加了也進不去,非常不好調控,所以在掌握原理的時候必須要掌握的清清楚楚的。那么接下來講講struts2傳遞參數的三種方案,分別如下:首先給你一個URL
http://16.158.70.172:8080/wstax-admin/report/assetsTransactionsByRegionTime?startTime=2015-06-01&endTime=2015-06-10&_=1434004102399
如上圖,分析這個URL如下,前面的16.158.70.172是IP地址,相當於localhost,相當於127.0.0.1.然后是項目名稱wstax-admin,然后是路徑名稱,然后我們看這個action name="assetsTransactionsByRegionTime"其后傳過來三個參數,startTime和endTime分別是起始和截止時間,然后是后面的_1434004102399這個字符串,這是由於get請求的時候,加一個由時間隨機生成的字符串,這樣保證了每個url不同,這樣每次就不會再去取緩存中的東西,而是去服務器上的東西,保證每次取的資源都是更新過后最新的資源。現在我們攔截了這個請求,傳參的方法是在Action中,定義一個跟參數完全相同名字的變量,寫getter和setter方法。如下:
@Controller("dailyMonitoringAction") @Scope("prototype") public class DailyMonitoringAction extends BaseAction { private static final long serialVersionUID = -2065341145635610669L; @Autowired private IDailyMonitoringService dailyMonitoringService; private String startTime = null; private String endTime = null; public String getStartTime() { return startTime; } public void setStartTime(String startTime) { this.startTime = startTime; } public String getEndTime() { return endTime; } public void setEndTime(String endTime) { this.endTime = endTime; } }
然后你看我們的Action中,
/*SpringMVC傳遞參數和Struts傳遞參數 不同; Struts會調用setter方法來將值返回*/ public String loadAssetsTransactionsByRegionTime() { lineVM = new LineChartVM(); lineVM.setTitle("Assets Transactions By Region"); lineVM.setyAxisName("Transactions"); Map<String, Map<String, Double>> assetRegionMap = dailyMonitoringService .loadAssetRegionTransactionTime(startTime, endTime); lineVM.setCategories(new ArrayList<String>(assetRegionMap.keySet())); Map<String, List<Double>> seriesMap = pivotingMap(assetRegionMap, 0D); List<ChartSerieVM> seriesList = new ArrayList<ChartSerieVM>(); for (String key : seriesMap.keySet()) { ChartSerieVM chartSerieVM = new ChartSerieVM(); chartSerieVM.setName(key); chartSerieVM.setData(seriesMap.get(key)); seriesList.add(chartSerieVM); } lineVM.setSeries(seriesList); return SUCCESS; }
你看我們的startTime和endTime是直接使用的,沒有在函數的參數中寫,而且定義的時候我們定義的是private String endTime = null;但是使用的時候,值就這么直接傳遞了進來,就是這么神奇啊。另外兩種參數傳遞方法是ActionContext.getContext().put("startTime","2015-06-01");ActionContext.getContext.put("endTime","2015-06-10");(其中put進去的一對對的鍵值對)和通過Servlet的API來傳值(ServletActionContext.getRequest.setAttribute("startTime","2015-06-01");ServletActionContext.getRequest.setAttribute("endTime","2015-06-10");)
前台在展現數據時候,可以有如下幾種方法:
1.${startTime} ${endTime}直接取值.
2.通過struts2的標簽庫<%@taglib prefix="s" uri="/struts-tags"%> 引入struts2jar包中的一個tags標簽庫,然后使用如下方式:
<s:property value="#startTime"> <s:property value="#endTime">
就可以將數據展現出來。(注意這里的value中的變量名前面要加‘#’號。)
備注:對於ServletActionContext.getRequest.setAttribute("endTime","2015-06-10");這種取值方式,在前台展示的時候需要這樣來用,如下:
<s:property value="#request.endTime">
************************************************************************************************************************************
接下來我們看看Struts中最核心的知識點:
:
鳴謝:
參考博客(http://www.cnblogs.com/suxiaolei/archive/2011/10/28/2228063.html)