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


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)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM