Struts 2.3.24源碼解析+Struts2攔截參數,處理請求,返回到前臺過程詳析

Struts2官網:http://struts.apache.org/html

目前最新版本:Struts 2.3.24前端

Struts1已經徹底被淘汰了,而Struts2是借鑑了webwork的設計理念而設計的基於MVC的框架。感興趣的能夠了解一下webwork概念,這裏不作涉及。咱們常說的Struts2 Spring Hibernate三大面試中常被問到的框架,其實Struts2和SpringMVC是兩個獨立的MVC框架。並且SpringMVC是目前最好的MVC框架(我的感受),可是因爲舊的一些項目運維須要Struts2框架的知識,故而作整理。web

下載了Struts2框架以後,咱們要學習什麼?1.學習框架運做的整個流程;2.學習如何搭建框架;3.本身動手寫一個。先來看看框架的流程,以下:面試

Struts2(因爲Struts1表現層單一,沒法跟Freemarker等技術整合),它採用攔截器的機制來處理用戶的請求。apache

先來說講Struts2的原理圖,如上圖所示:後端

  1.當用戶發起請求時(一個URL),服務器端的Web容器收到了請求。api

  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的全部攔截器以後,返回給主控制器。主控制器根據此結果從配置文件中找到真正的路徑,而後將請求轉發給相應的視圖。app

  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)

相關文章
相關標籤/搜索