SSH框架之struts2專題3:Struts2核心

1 在Action中獲取Servlet API

  • 爲了不與Servlet API耦合,Struts2對HttpServletRequest、HttpSession、ServletContext進行了封裝,構造了三個Map對象來代替這三種對象。固然,也能夠獲取到真正的這三個Servlet的API。
  • 在Action中獲取這三個對象的方式有三種。

    1.1 經過ActionContext獲取

  • 在Struts2框架中,經過Action的執行上下文類ActionContext,能夠獲取到request/session/application對象。
    ActionContext ctx = ActionContext.getContext();
    Map<String,Object> application = ctx.getApplication();
    Map<String,Object> session = ctx.getSession();
  • ActionContext對象自己就是request範圍的存儲空間(如今暫時這樣認爲)。因此,對於向request範圍中添加屬性,直接向ActionContext對象中添加便可。

    1.2 經過ServletContext獲取

  • 經過ActionContext獲取的request、session、application均爲Map對象,並不是真正的Servlet API。而經過ServletActionContext,能夠獲取到真正的Http請求中的Servlet API對象。
    HttpServletRequest req = ServletActionContext.getRequest();
    HttpServletResponse res = ServletActionContext.getResponse();
    ServletContext application = ServletActionContext.getServletContext();
    HttpSession session = ServletActionContext.getRequest().getSession();

    1.3 經過實現特定接口來獲取

  • 經過讓Action實現特定接口,也可獲取request/session/application對象。不過須要注意的是,這種獲取方式獲取的爲Map,而並不是是真正的Servlet API。
    //RequestAware 接口:該接口中只有一個方法
    public void setRequest(Map<String,Object> request)
    //SessionAware 接口:該接口中只有一個方法
    public void setSession (Map<String,Object> session)
    //ApplicationAware 接口:該接口中只有一個方法
    public void setApplication (Map<String,Object> application)

    2 OGNL與值棧

    2.1 OGNL

    2.1.1 OGML與Struts2的關係

  • Struts2中不少地方都使用了OGNL。因此在這裏咱們要學習Struts2中有關OGNL的知識。
  • 對於OGNL,首先要清楚,OGNL是個第三方的開源項目,其自己是與Struts2沒有任何關係的。OGNL表達式的計算須要經過一個Map進行,而該Map有一個名稱叫作上下文context。這個context的Map中已經存放了若干對象,這些對象分爲兩類:根對象和非根對象。
  • Struts2中使用了ActionContext做爲上下文來計算OGNL,ActionContext中的根對象只有一個,叫作值棧ValueStack。其他對象均爲非根對象。

2.1.2 什麼是OGNL?

  • OGNL是Object-Graph Navigation Language的縮寫,它是一種功能強大的表達式語言,是一個第三方的開源項目。
  • Struts2經過使用OGNL簡單一致的表達式語法,能夠存取對象的任意屬性,調用對象的方法,便利整個對象的結構圖,實現字段類型轉化等功能。
  • Struts2經過jar包ognl-3...jar將OGNL項目引入。
    SSH框架之struts2專題3:Struts2核心

2.1.3 OGNL的特色

  • 相對其餘表達式語言,它提供了更加豐富的功能:
  • 一、支持對象方法調用,如xxx.sayHello()。
  • 二、支持類靜態方法調用和常量訪問,表達式的格式爲:@[全限定性類名]@[方法名 | 常量名]。例如,@java.lang.Integer@parseInt("123")或者是@java.lang.Math@PI。不過對於靜態方法的訪問,須要經過在Struts2的配置文件struts.xml中設置常量struts.ognl.allowStaticMethodAccess的值爲true進行開啓。
  • 三、能夠操做集合對象。
  • 四、能夠直接建立對象。

2.1.4 OGNL文檔解讀

  • 查看Struts2的有關OGNL的幫助文檔,Struts2解壓目錄下的docs/docs/home.html。
    SSH框架之struts2專題3:Struts2核心
  • (Struts2)框架使用了一個「標準命名上下文」來計算OGNL表達式。用於處理OGNL的最頂層對象是一個Map(一般被稱之爲上下文Map或者是上下文)。在上下文Map中,OGNL有一個根對象的概念。在表達式中,根對象的引用不用使用任何「標記」,而引用其餘對象則須要使用#標記。
    SSH框架之struts2專題3:Struts2核心
  • (Struts2)框架將ActionContext設置爲OGNL上下文對象,將值棧設置爲OGNL根對象(值棧是一個包含多個對象的集合,可是對於OGNL來講,它是做爲一個對象出現的)。和值棧一塊兒,框架也放置了其餘對象到ActionContext中,其中包含表現爲application、session或者request上下文的Map。這些對象將於值棧中的數據共存於ActionContext中。
    SSH框架之struts2專題3:Struts2核心
  • 從以上文檔的閱讀中可知,OGNL有一個上下文概念,即Context,用於存放數據。OGNL的上下文其實質就是一個Map,其中存放着不少的JavaBean對象。這些對象根據對其操做方式的不一樣分爲兩類:根對象和非根對象。對於非根對象,須要使用#來訪問,而對於根對象,則能夠直接訪問。
  • 不管是根對象仍是非根對象,在Struts2中均是用於在應用中共享數據的。通常狀況下,會在Action方法中存入數據,而在JSP頁面中讀取數據。

2.2 值棧

  • 對於Sturts2中的值棧的學習,主要是要搞清楚如下幾個對象間的關係:
    一、值棧與ActionContext的關係。
    二、值棧與值棧的Root屬性的關係。
    三、值棧的Context屬性和ActionContext的關係。
  • 它們的關係,經過如下幾個知識點的學習能夠加深理解。

2.2.1 值棧的建立

  • 在用戶提交一個Action請求後,系統會立刻建立兩個對象:Action實例與值棧對象。Struts2中的值棧ValueStack是個接口,其實現類爲OgnlValueStack。
  • 在代碼中選中ValueStack接口,Ctrl + T能夠查看到該接口的實現類爲OgnlValueStack。
    SSH框架之struts2專題3:Struts2核心

2.2.2 值棧的組成

  • 打開OgnlValueStack類的源碼能夠看到,OgnlValueStack類包含兩個成員:root與context。
    SSH框架之struts2專題3:Struts2核心
    a、root的類型
  • 屬性root爲CompoundRoot類型,打開CompoundRoot源碼發現,該類型的本質爲ArrayList。
    SSH框架之struts2專題3:Struts2核心
  • 即CompoundRoot爲加強的ArrayList,是一個棧:有截取棧元素、獲取棧頂元素、彈棧、壓棧操做。
    SSH框架之struts2專題3:Struts2核心
  • ArrayList在存放數據時的特色是,ArrayList只可以順序存放數據,而沒法爲該數據命名。例如,對於一個Student類對象stu,其在List中的存放可能爲list.add(stu);即只是將stu所攜帶的數據---屬性值存放到了List中,可是stu這個對象並無名稱。不像將數據存放到Map中map.put("student", stu);這樣,能夠爲stu這個對象起名爲「student」。
  • 進一步分析CompoundRoot源碼中的peek()方法發現,其只能獲取棧頂元素,而棧頂元素就是一個Student對象。因爲該棧爲List結構,沒法經過名稱獲取棧頂元素。因此對於這個Student對象,只需也只可以經過屬性名稱來獲取,如name、age等就能夠直接讀取該棧頂元素的指定屬性值。
  • 綜上所述,對於root中的對象的訪問,即對於根對象的訪問,只需經過對象的屬性名即可讀取。

b、context的建立html

  • 值棧的屬性context的類型爲Map<String, Object>,查看OgnlValueStack類的setRoot()方法源碼能夠看到,在初始化root的同時建立了context對象。
    SSH框架之struts2專題3:Struts2核心

    2.2.3 值棧的獲取比較麻煩

  • 當一個Action請求到來時,不只會建立一個Action實例,還會建立一個ValueStack對象,用於存放Action運行過程當中的相關數據。當該請求結束時,Action實例消失,用於記錄其運行期間數據的值棧也就沒有了意義。因此,當請求結束時,同時須要將值棧對象銷燬,即值棧的生命週期與請求Request的相同。爲了保證這一點,就將值棧對象經過setAttribute()方法,將其放入到了Request域屬性中,並將該屬性的key以常量的形式保存在ServletActionContext中。因此,爲了獲取值棧對象,首先須要獲取到Request對象,而後再獲取到其key,這樣纔可以獲取到值棧對象。這個過程比較麻煩。
    SSH框架之struts2專題3:Struts2核心
    //從request中獲取ValueStack對象
        String key = ServletActionContext.STRUTS_VALUESTACK_KEY;
        HttpServletRequest request = ServletActionContext.getRequest();
        ValueStack vs = (ValueStack) request.getAttribute(key);

    2.2.4 context屬性的別名ActionContext

  • 在Strut2中,值棧的context屬性所起的做用是比較大的,且在代碼中須要常常性地訪問。可是從以上分析中可知,想要獲取到context屬性,首先要獲取到值棧對象,而獲取值棧對象自己就是一個比較麻煩的過程,這就致使獲取值棧的context屬性會更加麻煩。
  • 爲了方便獲取值棧的context屬性,Struts2專門爲其又起了個別名---ActionContext,經過ActionContext的getContext()就能夠直接獲取到值棧的context屬性。
    SSH框架之struts2專題3:Struts2核心
  • ActionContext類中查看getContext()方法,有一個靜態對象actionContext,查看其定義,能夠看到其是一個ThreadLocal對象,而ThreadLocal對象本質就是一個Map<Thread, Object>,即這個actionContext靜態對象的本質是一個Map<Thread, ActionContext>。
    SSH框架之struts2專題3:Struts2核心
  • 再查看這個ThreadLocal的get()方法源碼能夠看到,get()方法底層調用的是Map的get()方法,其key爲當前線程。這樣就保證了在當前線程中獲取到的Context是同一個Context。
    SSH框架之struts2專題3:Struts2核心

    2.2.5 值棧的獲取根簡單

  • 經過前面的敘述中可知,值棧的獲取至關麻煩,可是值棧又很重要,須要常常訪問。而如今值棧的context屬性,這個Map<String, Object>獲取起來又很簡單。爲了方便對值棧的訪問,因而就將值棧對象直接存放到了context這個Map中。經過查看OgnlValueStack的對root的初始化方法setRoot()能夠看到。(注意:存放的是值棧對象的內存地址,因此不存在大包小,小包大的問題)
    SSH框架之struts2專題3:Struts2核心
  • 這樣就能夠經過如下語句直接獲取到值棧對象:
    ValueStack vs2 = ActionContext.getContext().getValueStack();
  • getValueStack()方法的本質就是調用Map的get()方法,經過制定值棧的key獲取值棧對象。
    SSH框架之struts2專題3:Struts2核心

    2.2.6 值棧的棧操做

  • 查看OgnlValueStack類中的peek()、pop()、push()方法可知,對valueStack對象的棧的操做,本質是對root棧對象的操做。即從宏觀上看能夠直接說值棧就是根對象。可是其實根對象指的是值棧中的root對象,而非根對象是值棧中的context對象。
    SSH框架之struts2專題3:Struts2核心

2.3 獲取值棧對象

2.3.1 從request中獲取值棧對象

  • 每一個請求都會建立一個Action,而每一個Action實例,都會擁有一個ActionContext實例,每一個ActionContext中都包含一個值棧。值棧會貫穿Action的整個生命週期,即與請求request的生命週期相同。爲了保證這一點,就將值棧建立在request做用域中。即將值棧放入到request的一個屬性中,當請求結束,request被銷燬時,值棧空間被收回。
  • 值棧的實質是request中的一個屬性值,這個屬性的名稱爲:struts.valueStack,保存在ServletActionContext的常量STRUTS_VALUESTACK_KEY中。
    SSH框架之struts2專題3:Struts2核心
  • 從以上的敘述中可知,從HttpServletRequest中獲取,即從底層獲取值棧對象的方式爲:
    HttpServletRequest request = ServletActionContext.getRequest();
    ValueStack myValueStack = (ValueStack) request.getAttribute(
    ServletActionContext.STRUTS_VALUESTACK_KEY);

    2.3.2 從ActionContext中獲取值棧對象

  • 從以前對文檔的閱讀可知,在Struts2中OGNL上下文Context接口中的實現類爲ActionContext,即ActionContext爲Map結構。該Map中事先已經存放好了一組對象,這組對象中就包含根對象值棧。其他爲非根對象。
    SSH框架之struts2專題3:Struts2核心
  • 由此可知,值棧也可從ActionContext中直接獲取。
    ValueStack myValueStack2 = ActionContext.getContext().getValueStack();

    2.4 值棧操做

    2.4.1 搭建測試環境

    一、定義實體Student:java

    package com.eason.valuestack.entity;
    public class Student {
        private String name;
        private int age;
        public Student(String name, int age) {
            super();
            this.name = name;
            this.age = age;
        }
        public Student() {
            super();
            // TODO Auto-generated constructor stub
        }
        @Override
        public String toString() {
            return "Student [name=" + name + ", age=" + age + "]";
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            this.age = age;
        }
    }

    二、定義Ation正則表達式

    package com.eason.valuestack.entity;
        import java.util.HashMap;
        import java.util.Map;
        import com.opensymphony.xwork2.ActionContext;
        import com.opensymphony.xwork2.util.ValueStack;
        public class SomeAction {
            public String SomeAction() {
                Student student = new Student("張三", 21);
                //獲取值棧對象
                ValueStack valueStack = ActionContext.getContext().getValueStack();
                return "success";
            }
        }

    三、註冊Actionsql

    <struts>
        <package name="demo" namespace="/test" extends="struts-default">
            <action name="some" class="com.eason.valuestack.entity.SomeAction">
                <result>/show.jsp</result>
            </action>
        </package>
    </struts>

    四、定義視圖頁面showexpress

    <body>
        <s:debug/>
    </body>

    2.4.2 向root中顯式放入數據

  • 向root中顯式地放入數據,有五種方式。下面以向root中放入Student(name, age)對象爲例。
    一、經過操做值棧來添加無名稱對象
    • ValueStack即值棧,其實現類OgnlValueStack的對象即值棧對象。既然其稱之爲值棧,就應該有棧的相關操做,查看OgnlValueStack的源碼,能夠看到其確實有棧操做的方法。但,再看看這些方法的實現,其本質是調用了值棧對象的屬性根對象root的棧操做,即對值棧的直接操做,本質上是對根對象root的棧操做。
      SSH框架之struts2專題3:Struts2核心
  • 經過對值棧的壓棧操做向root中添加數據。因爲root的本質是ArrayList,其特色就是向其中添加的對象時沒法指定名稱的。
    public String execute() {
            Student student = new Student("張三", 21);
            //獲取值棧對象
            ValueStack stack = ActionContext.getContext().getValueStack();
            stack.push(student);
            return "success";
        }

二、經過操做root來添加無名稱對象apache

  • 從前面對root的源碼分析可知,對值棧的操做,本質上就是對值棧的root屬性的棧操做。因此,能夠直接向root中進行壓棧操做。
    public String execute() {
            Student student = new Student("張三", 21);
            //獲取值棧對象
            ValueStack stack = ActionContext.getContext().getValueStack();
            stack.getRoot().push(student);
            return "success";
        }
    <body>
        <s:debug/>
        ------------------root數據顯示--------------------<br/>
        name = <s:property value="name"/><br/>
        age = <s:property value="age"/><br/>
        ----------------el數據顯示------------------------<br/>
        el_name = ${name }<br/>
        el_age = ${age }<br/>
    </body>

    三、添加map對象編程

  • 將對象放入map中,再將map添加到root中。Map的特色是可指定向其中添加對象的名稱。
    public String execute() {
            Map<String, Student> map =  new HashMap<String, Student>();
            Student student = new Student("張三", 21);
            map.put("studentMap", student); 
            //獲取值棧對象
            ValueStack stack = ActionContext.getContext().getValueStack();
            stack.push(map);
            return "success";
        }
    <body>
        <s:debug/>
        ------------------root數據顯示--------------------<br/>
        name = <s:property value="studentMap.name"/><br/>
        age = <s:property value="studentMap.age"/><br/>
        ----------------el數據顯示------------------------<br/>
        el_name = ${studentMap.name }<br/>
        el_age = ${studentMap.age }<br/>
    </body>

四、直接添加有名稱對象數組

  • OgnlValueStack值棧中有一個方法set(),可向其中的對象指定名稱,其底層也是採用map來實現的。
    SSH框架之struts2專題3:Struts2核心
    public String execute() {
        Student student = new Student("張三", 21);
        //獲取值棧對象
        ValueStack stack = ActionContext.getContext().getValueStack();
        stack.set("studentMap", student);
        return "success";
    }

    五、將root做爲ArrayList來添加對象瀏覽器

  • 從前面對root的源碼分析可知,root的本質是ArrayList,因此能夠調用其add()方法來添加數據。
    public String execute() {
        Student student = new Student("張三", 21);
        //獲取值棧對象
        ValueStack stack = ActionContext.getContext().getValueStack();
        stack.getRoot().add(student);
        return "success";
    }

    可是,須要注意的是,經過add()添加的數據,是將root做爲ArrayList來使用,而ArrayList的add()方法會自動將數據放入到list的最後。就本例來講,經過add()添加的數據,會放入到root棧的棧底。而經過前面棧的操做所添加的數據,是將數據放入到root棧頂。
    SSH框架之struts2專題3:Struts2核心緩存

    2.4.3 向root中隱式放入數據

  • 當Struts2接收到一個請求後,會立刻建立一個Action對象,而後爲該Action對象建立一個ActionContext對象,那麼也就建立了值棧對象。
  • 值棧對象建立好以後,首先會將建立好的Aciton對象直接放入到值棧的棧頂。因而,JSP頁面對於Action屬性的訪問,直接寫上Action屬性名稱便可。
    SSH框架之struts2專題3:Struts2核心
    public class SomeAction {
        private String name;
        private int age;
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            this.age = age;
        }
        public String execute() {
            ......
            return "success";
        }
    }

    2.4.4 向context中顯式放入數據

  • 向context中放入數據,就是向map中放入數據,須要指定key。Struts2中已經定義好一些key,用於完成特定數據功能。訪問context中的數據,即爲非根數據,須要使用#。
    SSH框架之struts2專題3:Struts2核心
    一、向context中直接放入數據
  • 向context中直接放入數據,至關於在context中添加了用戶自定義的key和value,即map對象。
    public String execute() {
        Student student = new Student("張三", 21);
        ActionContext.getContext().put("myStudent", student);
        return "success";
    }
  • 向ActionContext中直接寫入數據,即造成以下狀況:
    SSH框架之struts2專題3:Struts2核心
  • 其訪問方式即爲:
    <body>
        <s:debug/>
        ---------------------context數據顯示----------------------<br/>
        name = <s:property value="#myStudent.name"/>
        age = <s:property value="#myStudent.age"/>
    </body>
  • 固然,因爲context自己是request範圍,那麼向context中直接添加數據,也即放入request範圍中數據。此時,JSP頁面可經過系統定義好的名稱爲request的key來訪問。
    <body>
        <s:debug/>
        ---------------------context數據顯示----------------------<br/>
        name = <s:property value="#request.myStudent.name"/>
        age = <s:property value="#request.myStudent.age"/>
    </body>

    二、向context的ssession中放入數據

  • 向context的session中放入數據,即將對象放入到session範圍。
    public String execute() {
            Student student = new Student("張三", 21);
            ActionContext.getContext().getSession().put("myStudent", student);
            return "success";
        }
    <body>
    <s:debug/>
    ---------------------context數據顯示----------------------<br/>
    name = <s:property value="#session.myStudent.name"/>
    age = <s:property value="#session.myStudent.age"/>
    </body>

    三、向context的application中放入數據

  • 向context的application中放入數據,即將對象放入到application範圍中。
    public String execute() {
        Student student = new Student("張三", 21);
        ActionContext.getContext().getApplication().put("myStudent", student);
        return "success";
    }
    <body>
    <s:debug/>
    ---------------------context數據顯示----------------------<br/>
    name = <s:property value="#application.myStudent.name"/>
    age = <s:property value="#application.myStudent.age"/>
    </body>

    四、JSP頁面經過稱之爲attr的key讀取數據

  • 若JSP頁面經過名稱爲attr的key讀取數據,系統會依次從page、request、session、application範圍來查找指定key的數據。若沒有找到,則爲其賦值爲null。
    <body>
        <s:debug/>
        ---------------------context數據顯示----------------------<br/>
        name = <s:property value="#attr.myStudent.name"/>
        age = <s:property value="#attr.myStudent.age"/>
    </body>

    2.4.5 向context中隱式放入數據

  • 有兩種數據是在用戶不知情的狀況下自動放入到context中的。
    一、請求參數
  • 在提交Action請求時所攜帶的參數,會自動存放到context的parameters屬性中。
    SSH框架之struts2專題3:Struts2核心
  • 須要注意的是,提交action請求時所攜帶的參數,參數會寫入到context中的parameters中。
    SSH框架之struts2專題3:Struts2核心
    SSH框架之struts2專題3:Struts2核心
  • 固然,在struts配置文件中Action重定向時提交的參數,若重定向到Action,但Action中有屬性用於接該參數,則是放入到root中的。若重定向到的Action中無屬性接收該參數,或者直接重定向到頁面,則是將數據放入到context中key爲parameters的map中。
    二、Action對象
  • 當Action實例建立好後,不只會自動將該Action的屬性放入到root的棧頂,並且還會將Action實例自己也放入到context的action屬性中。
    SSH框架之struts2專題3:Struts2核心
    SSH框架之struts2專題3:Struts2核心

    2.4.6 數據的加載順序

    一、root中數據的加載順序

  • 當在Action中僅僅向context放入某數據some後,頁面不管是經過#從context中讀取數據,仍是不使用#直接從root中讀取數據,都可獲得放入的值。
  • 對於從root中讀取數據,其底層調用了值棧的findValue()方法。查看ValueStack的實現類OgnlValueStack源碼,方法間的調用關係以下:
    SSH框架之struts2專題3:Struts2核心
  • 從以上源碼可知,<s:property /> 會先從root中加載數據,若不存在該數據,則會從context 中加載,而不會直接設值爲null。
    二、request中數據的加載順序
  • 當在Action中向context與root中分別爲某數據some賦予不一樣的值時,頁面從context中與root中可獲取其相應的值。但若從#request中獲取some值,會發現其獲取的與root中的值相同。
    SSH框架之struts2專題3:Struts2核心
  • Struts2將HttpServletRequest進行了再次包裝,加強了其功能。在JSP頁面中經過對錶達式<%= request %>的輸出,可看到其類型已經再也不是HttpServletRequest,而是org.apache.struts2.dispatcher.StrutsRequestWrapper。
  • <s:property/>對於從request中讀取數據,其底層調用了StrutsRequestWrapper的getAttribute()方法。查看StrutsRequestWrapper類的源碼:
    SSH框架之struts2專題3:Struts2核心
  • 從以上源碼可知,若request範圍的屬性值不空,則直接取該值;若爲空,則再調用值棧的findValue()方法。即找到,則取root中的值;root中沒有找到,再從context中查找。
  • 從這個分析可知,以前所說的ActionContext.getContext()所獲取的爲request範圍,並不許確,或者說,並不正確。由於如今從#request中獲取的值並非context中的值,而是root中的值。

    2.5 OGNL對於集合的操做

  • Struts2中使用OGNL對於集合的操做主要涉及如下幾種狀況:
    一、建立List與Map集合
    二、遍歷LIst與Map集合
    三、集合元素的判斷
    四、集合投影
    五、集合過濾

    2.5.1 建立List與Map集合

  • OGNL表達式中使用以下形式能夠建立相應集合:
  • List集合的建立:{元素1, 元素2, ...}
  • Map集合的建立:#{'key1':value1, 'key2':value2, ...}
  • 固然能夠藉助於Struts2標籤<s:set/>建立一個有名稱的集合對象:
    SSH框架之struts2專題3:Struts2核心
  • 此時,建立了一個名爲userList的List<String>集合,其包含三個字符串元素,"zs","ls","ww"。
    SSH框架之struts2專題3:Struts2核心
  • 此時,建立一個名爲userMap的Map<String, Object>集合,其包含兩對元素:("name", "zs")與("pwd", 21)。

    2.5.2 遍歷List與Map集合

  • <s:set/>標籤還有一個屬性 scope,用於指定要將此 List 或 Map 存放入哪一個範圍。其值
    能夠爲:application、session、request(放入 context 中)、action(放入 root 中)。
  • 若不指定範圍,則會將此變量直接放入 ActionContext 中,與值棧、application、session
    等對象並列(以下圖),造成非根對象。因此要對它們進行訪問,須要加上#。如:#userList 或 #userMap。
    SSH框架之struts2專題3:Struts2核心
    一、對於List的遍歷
  • 若要對集合進行遍歷,則須要藉助<s:iterator/>標籤。
    SSH框架之struts2專題3:Struts2核心
  • <s:iterator/>標籤有個特色,會將當前迭代的對象臨時放入值棧的棧頂。而<s:property/>
    標籤也有個特色,就是在不指定 value 屬性時,會輸出值棧棧頂元素的值。
    二、對Map的遍歷
  • 方式一:直接輸出棧頂的Map對象。
    SSH框架之struts2專題3:Struts2核心
  • 方式二:當前迭代的對象命名爲變量 entry,該對象會被臨時放入到值棧棧頂。因此對於該對象
    的顯示,無需使用#,直接輸出便可。
    SSH框架之struts2專題3:Struts2核心
  • 方式三:Map 的元素爲 Map.Entry 對象,而 Map.Entry 有兩個屬性:key 與 value。當迭代 Map
    對象時,會將當前迭代的元素對象,即 Map.Entry 對象放入棧頂。即將 key 與 value 屬性放
    入了棧頂。
    SSH框架之struts2專題3:Struts2核心

    2.5.3 集合元素的判斷

  • 對於集合類型,可使用雙目運算符 in 和 not in 組成表達式,來判斷指定元素是否爲
    指定集合的元素。其兩個運算數爲 OGNL 表達式:e1 in e2 或 e1 not in e2。
  • 其中,in用來判斷e1是否在集合對象e2中;not in判斷e1是否不在集合對象e2中。
    其結果爲 boolean 類型。如:
    SSH框架之struts2專題3:Struts2核心
  • 其輸出結果爲true。

    2.5.4 集合投影

  • 所謂集合投影,指對於集合中的全部數據(至關於行),只選擇集合的某個屬性值(字段)做爲一個新的集合。即行數不變,只選擇某列。使用:集合.{字段名}投影。例如,定義好了三個Bean:
    SSH框架之struts2專題3:Struts2核心
    SSH框架之struts2專題3:Struts2核心

    2.5.4 集合查詢

  • 集合查詢也稱之爲集合過濾,指的是對於集合中的全部數據,進行條件篩選,造成的結果集。即列數不變,只選擇部分行。集合查詢須要使用關鍵字this,表示當前檢索的對象。
  • 對於結果集的選擇,可使用如下三個符號:
  • ?:表示結果集的全部內容。
  • ^:結果集的第一條內容。
  • $:結果集的最後一條內容。
    一、?的使用
    SSH框架之struts2專題3:Struts2核心
  • 查詢結果:
    SSH框架之struts2專題3:Struts2核心
    二、^的使用
    SSH框架之struts2專題3:Struts2核心
  • 查詢結果:
    SSH框架之struts2專題3:Struts2核心

    3 動態調用方法

  • 若Action中存在多個方法,但在配置文件中註冊該Action時,並未爲每一個方法指定一個<action/>,而是隻爲這一個Action類註冊一個<action/>。那麼,當用戶訪問該<action/>時,到底執行哪一個方法,則是由用戶發出的請求動態決定。即僅從配置文件中是看不出<action/>標籤是對應哪一個方法的,只有在運行時根據具體的用戶請求,纔可以決定執行哪一個方法。這種狀況稱之爲動態調用方法。動態調用方法有兩種實現方式。

    3.1 動態方法調用

  • 動態方法調用是指,在地址欄提交請求時,直接在URL後跟上「!方法名」方式動態定義要執行的方法。
    -不過,動態方法調用默認是關閉的,能夠經過改變「動態方法調用」常量struts.enable.DynamicMethodInvocation的值來開啓動態方法調用功能。
    SSH框架之struts2專題3:Struts2核心
    SSH框架之struts2專題3:Struts2核心
  • 舉例:dynamicMethodInvoke
    SSH框架之struts2專題3:Struts2核心
    SSH框架之struts2專題3:Struts2核心
  • 地址欄中訪問:
    SSH框架之struts2專題3:Struts2核心

    3.2 使用通配符定義的Action

  • 使用通配符定義的action是指,在配置文件中定義action時,其name中包含通配符。請求URL中提交的action的具體值,將做爲的真實值。而<action/>中的佔位符將接收這個真實值。佔位符通常出如今method屬性中。
    SSH框架之struts2專題3:Struts2核心

    4 接收請求參數

    4.1 屬性驅動方式

  • 所謂屬性驅動方式是指服務器端接收來自客戶端的離散數據的方式。用戶提交的數據,Action 原封不動的進行逐個接收。該接收方式要求,在 Action 類中定義與請求參數同名的屬性,即,要定義該屬性的 set 方法。這樣就可以使 Action 自動將請求參數的值賦予同名屬性。
  • 舉例:receiveProperty
    SSH框架之struts2專題3:Struts2核心
    SSH框架之struts2專題3:Struts2核心
    SSH框架之struts2專題3:Struts2核心
    SSH框架之struts2專題3:Struts2核心

    4.2 域驅動方式

  • 所謂域驅動方式是指,服務器端以封裝好的對象方式接收來自客戶端的數據方式。將用
    戶提交的多個數據以封裝對象的方式進行總體接收。該方式要求,表單提交時,參數以對象屬性的方式提交。而 Action 中要將同名的對象定義爲屬性(爲其賦予 getter and setter)。這樣請求將會以封裝好的對象數據形式提交給 Action。
  • 舉例:receiveObject
    SSH框架之struts2專題3:Struts2核心
    SSH框架之struts2專題3:Struts2核心
    SSH框架之struts2專題3:Struts2核心
    SSH框架之struts2專題3:Struts2核心
    SSH框架之struts2專題3:Struts2核心
  • 控制檯輸出狀況:
    SSH框架之struts2專題3:Struts2核心
  • 對象類型數據接收的內部執行過程比較複雜:
    一、當將表單提交給服務器後,服務器首先會解析出表單元素,並讀取其中的一個元素name值,如讀取以下:
    SSH框架之struts2專題3:Struts2核心
    二、執行Action的getStudent()方法以獲取Student對象。判斷Student對象是否爲null,若爲null,則服務器會經過反射建立一個Student對象。
    三、因爲此時的Student對象爲null,因此係統會建立一個並調用setStudent()方法將student初始化。
    四、此時的student已非空,會將剛纔解析出的表單元素,經過調用其set方法,這裏是setName()方法,將用戶輸入的值初始化該屬性。
    五、再解析下一個表單元素,並讀取其name值。
    SSH框架之struts2專題3:Struts2核心
    六、再次執行Action的getStudent()方法以獲取Student對象。此時的Student對象已非空。
    七、將剛纔解析出的表單元素,經過調用其set方法,這裏是setAge()方法,將用戶輸入的值初始化該屬性。

    4.3 集合數據接收

  • 所謂集合數據接收是指,以集合對象方式接收數據。此狀況與域驅動數據原理是相同的。注意,集合與數組是不一樣的。
  • 舉例:receiveCollection
    SSH框架之struts2專題3:Struts2核心SSH框架之struts2專題3:Struts2核心
  • 此時用於接收集合數據的屬性,不能定義爲數組。由於數組長度是固定的,而集合長度是可擴展的。
    SSH框架之struts2專題3:Struts2核心
    SSH框架之struts2專題3:Struts2核心
    SSH框架之struts2專題3:Struts2核心

    4.4 ModelDriven方式

    • ModelDriven接收請求參數運行背後使用了Struts的核心功能ValueStack。Struts2的默認攔截器中存在一個攔截器ModelDrivenInterceptor。當一個請求通過該攔截器時,在這個攔截器中,首先會判斷當前要調用的Action對象是否實現了ModelDriven接口。若是實現了這個接口,則調用getModel()方法,並把返回值壓入ValueStack棧頂。
      SSH框架之struts2專題3:Struts2核心
    • 舉例:receiveModelDriven
      SSH框架之struts2專題3:Struts2核心
      SSH框架之struts2專題3:Struts2核心
      SSH框架之struts2專題3:Struts2核心
      SSH框架之struts2專題3:Struts2核心
      SSH框架之struts2專題3:Struts2核心

      4.5 Action對象是多例的

    • action對象是多例的。對於包含name和age屬性的RegisterAction,在Web中多個用戶同時進行訪問時,系統會爲每一個用戶建立一個RegisterAction實例,接收來自不一樣用戶的name和age。其值是各不相同,各不相干的。因此action也是線程安全的。
    • 對於同一個業務,Web容器只會建立一個Servlet實例。這個實例容許多個請求共享,即容許多個線程共享。例如對於LoginServlet,只要用戶登陸,不管幾個用戶,Web容器只會建立一個LoginServlet實例。若此時,將username與password定義爲LoginServlet的成員變量,那麼不一樣的用戶爲其賦值是不一樣的,後一個用戶的值均會覆蓋前一個用戶的值,將引發併發問題,線程會不安全。因此,對於Servlet的使用,是不能定義成員變量的。
    • 固然,也基於此,能夠爲Serlvet添加一個自增的成員變量做爲該Servlet的訪問計數器。

      5 類型轉換

      5.1引入

  • 在Struts2中,請求參數還能夠是日期類型。如,定義一個請求參數brithday爲Date類型,爲其賦值爲1949-10-01,則brithday接收到的不是字符串「1949-10-01」,而是日期類型 Sat Oct 01 00:00:00 CST 1949。
  • 舉例:typeconverter
    SSH框架之struts2專題3:Struts2核心
    SSH框架之struts2專題3:Struts2核心
    SSH框架之struts2專題3:Struts2核心
    SSH框架之struts2專題3:Struts2核心

    5.2 默認類型轉換器

  • Struts2默認狀況下能夠將表單中輸入的文本數據轉換爲相應的基本數據類型。這個功能的實現,主要是因爲Struts2內置了類型轉換器,這些轉換器在struts-default.xml中能夠看到其定義。
    SSH框架之struts2專題3:Struts2核心
  • 常見的類型,基本都可由String轉換爲相應類型。
  • 如int和Integer,long和Long,float和Float,double和Double,char和Character,boolean和Boolean,Date(能夠接收yyyy-MM-dd或者yyyy-MM-dd HH:mm:ss格式字符串),數組(能夠將多個同名參數,存放到數組中),集合:能夠將數據保存到List、Map中。

    5.3 自定義類型轉換器

  • 在上面程序中,若輸入非yyyy-MM-dd格式,如輸入yyyy/MM/dd格式,在結果頁面中還能夠正常看到yyyy/MM/dd的日期輸出。可是查看控制檯,卻給出了null值。其實底層已經發生了類型轉換失敗,拋出了類型轉換異常TypeConversionException。若要使得系統能夠接收其餘格式的日期類型,則須要自定義類型轉換器。
  • 查看DateConverter、NumberConverter等系統定義好的類型轉換器源碼,能夠看到它們都是繼承自DefaultTypeConverter類。因此,咱們要定義本身的類型轉換器,也要繼承自該類。在使用時,通常須要覆蓋其父類的方法convertValue(),用於完成類型轉換。
    SSH框架之struts2專題3:Struts2核心
  • 定義convertValue方法時須要注意,其轉換通常是定義爲雙向的。
  • 就本例而言,從瀏覽器表單請求到服務器端action時,是由String到Date的轉換,那麼,第一個參數value爲String類型的請求參數值,第二個參數toType爲要轉換爲的Date類型。
  • 當類型轉換成功,可是服務器端其餘部分出現問題後,須要返回原頁面。此時用戶填寫過的數據應從新回顯。回顯則是由服務器端向瀏覽器端的運行,須要將轉換過的Date類型從新轉換爲String。那麼,此時的value則爲Date類型,而toType則爲String。
  • 注意第一個參數value,若轉換方向爲從請求到action,則value爲字符串數組。由於請求中是容許攜帶多個同名參數的,例以下面表單中的興趣愛好項的name屬性爲hobby,其值就有可能爲多個值。
    SSH框架之struts2專題3:Struts2核心
  • 這時的這個同名參數,其實就是數組。Struts2爲了兼顧到這個多個同名參數的狀況,就將從請求到action方向轉換的value指定爲String[],而非String。其底層使用的API爲:String[] value = request.getParameterValues(...);。
  • 注意,對於服務器端向瀏覽器端的轉換,須要使用Struts2標籤訂義的表單纔可演示出。演示時,age元素填入非數字字符,birthday填寫正確。另外,向<action/>中添加input視圖,Action類須要繼承ActionSupport類。
  • 之因此要繼承自ActionSupport類,是由於ActionSupport類實現了Action接口,而該接口中定義INPUT字符串常量。一旦發生類型轉換異常TypeConversionException,系統將自動轉向input視圖。
  • 示例:
    一、定義index頁面:
    SSH框架之struts2專題3:Struts2核心
    二、定義Action
    SSH框架之struts2專題3:Struts2核心
    三、註冊Action
    SSH框架之struts2專題3:Struts2核心
    四、定義自定義日期類型轉換器
  • 注意,Date在這裏自動導入的是java.sql包,而咱們須要的是java.util包中的Date。
    SSH框架之struts2專題3:Struts2核心
  • 注意,此時定義的類型轉換器,不只能夠完成日期類型的轉換,還能夠實現如下的回顯:age填寫錯誤,birthday填寫正確。但,若age填寫正確,birthday填寫錯誤的話,則birthday日期格式不正確,則沒法回顯。由於birthday值爲null,此時發生的異常不是類型轉換異常,而是格式解析異常ParseException。ParseException的發生不會使得頁面跳轉到input視圖。因此若要使得日期格式不正確時跳轉到input視圖,則須要讓其拋出TypeConversionException異常。
    SSH框架之struts2專題3:Struts2核心
  • 定義好類型轉換器類後,須要註冊該轉換器,用於通知Struts2框架在遇到指定類型變量時,調用類型轉換器。
  • 根據註冊方式的不一樣以及應用範圍的不一樣,能夠將類型轉換器分爲兩類:局部類型轉換器和全局類型轉換器。
    一、局部類型轉換器
  • 局部類型轉換器,僅僅對指定Action的指定屬性起做用。若註冊方式爲,在Action類所在的包下放置名稱爲以下格式的屬性文件:ActionClassName-conversion.properties文件。其中ActionClassName是Action的類名,-conversion.properties是固定寫法。
  • 該屬性文件的內容應該遵循以下格式:屬性名稱=類型轉換器的全類名。
    SSH框架之struts2專題3:Struts2核心
    二、全局類型轉換器
  • 全局類型轉換器,會對全部Action的指定類型的屬性生效。其註冊方式爲,在src目錄下放置名稱爲xwork-conversion.properties屬性文件。該文件的內容格式爲:待轉換的類型=類型轉換器的全類名。就本例而言,文件中的內容爲:
    SSH框架之struts2專題3:Struts2核心
  • 注意,對於服務器端向瀏覽器端的轉換,即數據的回顯功能,須要使用Struts2標籤訂義的表單纔可演示出。演示時,age元素填入非數字字符,birthday填寫正確。

    5.4 類型轉換異常提示信息的修改

  • 類型轉換異常提示信息,是系統定義好的內容,若直接顯示到用戶頁面,會使得頁面顯得不友好。可是,類型轉換異常提示信息時能夠修改。
  • 類型轉換異常提示信息的修改步驟:
    一、Action所在包中添加名稱爲ActionClassName.properties的屬性文件,其中ActionClassName爲Action類的類名。
    二、在該文件中寫入內容:invalid.fieldvalue.變量名=異常提示信息

    5.5 接收多種日期格式的類型轉換器

  • 對於前面定義的MyDateConverter類型轉換器,不管是局部的仍是全局的,其轉換的日期格式只能是在轉換器中指定的格式:yyyy/MM/dd。不能是其餘格式,原來默認的格式也不行。那麼,如何使類型轉換器能夠處理多種日期格式的轉換呢?
  • 在以上項目的基礎進行修改
    一、獲取日期格式
  • 在類型轉換器中定義一個 private 方法,用於判斷並獲取到日期的格式,若全部指定格
    式均不符合,則直接拋出類型轉換異常,跳轉到表單頁面。
    SSH框架之struts2專題3:Struts2核心
    二、保存日期格式對象
  • 因爲從頁面到服務端方向是由String到Date類型的轉換,value爲待轉換的數據,因此能夠從這個String的數據中獲取到日期格式,而後將String轉換爲 Date。
  • 但若發生數據回顯,則須要使用相同格式的日期格式對象,將Date轉換爲String。此時的value爲沒有格式的Date 數據,因此須要想辦法獲取到 String 到 Date 轉換時的日期格式對象。也就是說,須要在 String 到 Date 轉換時,就將用到的日期格式對象保存下來,以備Date 到 String 轉換時使用。
  • 那麼,如何在 String 到 Date 轉換時,保存日期格式對象呢?能夠將其保存到 ServletAPI的域對象中。即保存到 request、session 或 application 中。
  • 當在 String 到 Date 的轉換時將日期格式對象保存在了 ServletAPI 的域對象中,在發生數據回顯,由 Date 到 String 轉換時,直接從相應的 ServletAPI 的域中獲取日期格式對象便可。
    SSH框架之struts2專題3:Struts2核心
    三、最終的類型轉換器定義
    SSH框架之struts2專題3:Struts2核心
    SSH框架之struts2專題3:Struts2核心

    6 數據驗證

  • 在Web應用程序中,爲了防止客戶端傳來的數據引起程序的異常,經常須要對數據進行驗證。輸入驗證分爲客戶端驗證與服務器端驗證。客戶端驗證主要經過 JavaScript 腳本進行,而服務器端驗證則主要是經過 Java 代碼進行驗證。
  • 爲了保證數據的安全性,通常狀況下,客戶端驗證與服務器端驗證都是要進行的。咱們這裏所講的是 Struts2 如何在服務端對輸入數據進行驗證的。Struts2 中的輸入驗證
    有兩種實現方式:手工編寫代碼實現,與基於 XML 配置方式實現。

    6.1 手工編寫代碼實現數據驗證

  • 在Struts2中,驗證代碼是在Action中完成的。因爲數據的使用者是Action方法,因此驗證過程須要在Action方法執行以前進行。驗證分爲兩類:對Action中全部方法執行前的驗證;對Action中指定方法執行前的驗證。
  • 如下程序舉例,均根據以下的需求來作:
  • 登陸表單中提供用戶名與手機號兩個輸入。要求,用戶名和手機號不能爲空,而且手機號要符合手機號碼的格式:以1開頭,後跟3/4/5或者8,最後9位數字。驗證就是要驗證用戶名和手機號。

    6.1.1 對Action中全部方法執行前的驗證

  • 首先,須要進行數據驗證的Action類要繼承自ActionSupport類。而後,重寫validate()方法。Action中全部Action方法在執行以前,validate()方法均會被調用,以實現對數據的驗證。
  • 當某個數據驗證失敗時,Struts2會調用addFieldError()方法向系統的fieldErrors集合中添加驗證失敗信息。若是系統的fieldErrors集合中包含失敗信息,struts2會將請求轉發到名爲input的Result。在input視圖中能夠經過<s:fielderror/>顯示失敗信息。
  • ActionSupport類的addFieldError()方法含有兩個參數:第一個參數必須爲該Action的屬性名的字符串,用於指定驗證出錯的屬性名,即數據。第二個參數爲字符串,是錯誤提示信息。
    SSH框架之struts2專題3:Struts2核心
  • 舉例:validate1
    一、在index.jsp中使用絕對路徑,不然在input轉向時會出錯:
  • 注意,這裏使用基礎路徑將相對路徑自動變爲絕對路徑,基礎路徑會自動加在本頁面中全部的相對路徑以前。將相對路徑變爲絕對路徑。而基礎路徑的定義,在任何一個新建JSP文件中均有。
    SSH框架之struts2專題3:Struts2核心
    二、定義Action類:
    SSH框架之struts2專題3:Struts2核心
    三、對當前Ation中全部的方法被調用前均執行該驗證方法:
    SSH框架之struts2專題3:Struts2核心
    四、若是系統的fieldErrors集合中包含失敗信息,將請求轉發到名爲input的Result:
    SSH框架之struts2專題3:Struts2核心

    6.1.2 對Action中指定方法執行前的驗證

  • 經過在Action中定義public void validateXxx()方法來實現。validateXxx()方法只會驗證action中方法名爲xxx的方法。其中Xxx的第一個字母要大寫。
  • 當數據驗證失敗時,調用addFieldError()方法,也一樣會向系統的fieldErrors集合中添加驗證失敗信息。
  • 舉例:validate2
    SSH框架之struts2專題3:Struts2核心

    6.2 基於XML配置方式實現輸入數據驗證

  • 使用 XML 配置方式實現輸入數據的驗證,Action 類仍須要繼承自 ActionSupport 類。驗證仍分爲兩類:對 Action 中全部方法執行前的驗證;對 Action 中指定方法執行前的驗證。
  • 該驗證方式須要使用 XML 配置文件,該配置文件的約束,即文件頭部,在xwork-core-2.3.24.jar 中的根下的 xwork-validatior-1.0.3.dtd 中能夠找到。
    SSH框架之struts2專題3:Struts2核心
    SSH框架之struts2專題3:Struts2核心
  • 驗證配置文件的格式、內容以下:
    SSH框架之struts2專題3:Struts2核心
  • 驗證器是由系統提供的,系統已經定義好了 16 種驗證器。這些驗證器的定義能夠在
    xwork-core-2.3.24.jar 中的最後一個包 com.opensymphony.xwork2.validator.validators 下的
    default.xml 中查看到。
    SSH框架之struts2專題3:Struts2核心
    SSH框架之struts2專題3:Struts2核心

    6.2.1 對Action中全部方法執行前的驗證

  • 在Action類所在的包中放入一個XML配置文件,該文件的取名應遵照ActionClassName-validation.xml規則。其中ActionClassName爲Action的簡單類名,-validation爲固定寫法。例如,Action類爲com.abc.actions.UserAction,那麼該文件的取名應爲:UserAction-validation.xml。
  • 舉例:validate3
  • 拷貝validate1,將Action中驗證方法刪除,並在Action所在包中添加驗證配置文件。
    SSH框架之struts2專題3:Struts2核心
    SSH框架之struts2專題3:Struts2核心

    6.2.2 對Action中指定方法執行前的驗證

  • 因爲在struts.xml中,一個<action>標籤通常狀況下對應一個action方法的執行。因此,若要對Action類中指定方法進行執行前的驗證,則須要按以下規則命名配置文件:ActionClassName-ActionName-validation.xml。其中ActionName指的是struts.xml中<action>標籤的name屬性值。
  • 固然,該配置文件也是放在該Action的同一個包中的。
  • 舉例,validate4(拷貝validate3,在其基礎上修改)
    SSH框架之struts2專題3:Struts2核心
    SSH框架之struts2專題3:Struts2核心
    SSH框架之struts2專題3:Struts2核心

    6.2.3 經常使用驗證器用法

    一、required:非空(必填)驗證器

    <field-validator type="required">
    <message>性別不能爲空!</message>
    </field-validator>

    二、requiredstring:非空字符串驗證器

    <field-validator type="requiredstring">
    <param name="trim">true</param>
    <message>用戶名不能爲空!</message>
    </field-validator>

    三、fieldexpression:字段表達式關係判斷

    <field-validator type="fieldexpression">
    <param name=「expression」>pwd == repwd</param>
    <message>確認密碼與密碼不一致</message>
    </field-validator>
  • 注意,假設pwd與repwd是表單中的兩個元素的name屬性值,且表達式就是pwd==repwd,而非pwd!=repwd。
    四、stringlength:字符串長度驗證器
    <field-validator type="stringlength">
    <param name="minLength">2</param>
    <param name="maxLength">10</param>
    <param name="trim">true</param>
    <message>產品名稱應在${minLength}-${maxLength}個字符之間</message>
    </field-validator>

    五、int:整數範圍校驗器

    <field-validator type="int">
    <param name="min">1</param>
    <param name="max">150</param>
    <message>年齡必須在 1-150 之間</message>
    </field-validator>
  • 注意,int、long、short、double與date校驗器均繼承自RangeValidatorSupport<T>類,是範圍校驗器,即不對數據類型進行驗證,只對其有效範圍進行校驗。它們均由兩個參數T min 和T max。
    六、email:郵件地址校驗器
    <field-validator type="email">
    <message>電子郵件地址無效</message>
    </field-validator>

    七、regex:正則表達式校驗器

    <field-validator type="regex">
    <param name="regexExpression"><![CDATA[^1[3458]\d{9}$]]></param>
    <message>手機號格式不正確!</message>
    </field-validator>
  • 注:<![CDATA[……]]>稱爲 cData 區,用於存放特殊表達式。

    6.2.4 輸入驗證的執行流程

  • 若以上四種輸入驗證方式均進行了設置,則其執行順序以下:
    一、首先執行基於XML的驗證。系統按照下面順序尋找校驗文件:ActonClassName-validation.xml --> ActionClassName-ActionName-validation.xml
  • 當系統尋找到第一個校驗文件後,會繼續搜索後面的校驗文件。當搜索到全部校驗文件後,會把校驗文件中的全部校驗規則彙總,而後所有應用於處理方法的校驗。若是兩個校驗文件中指定的校驗規則衝突,則使用指定方法的校驗文件的校驗規則。
    二、執行Action中的validateXxx()方法。
    三、執行Action中的validate()方法。
    四、通過上面的執行,若是系統中的fieldErrors存在異常信息(即存放異常信息的集合的size大於0),系統自動將請求轉發至名稱爲input的視圖。若是系統中的fieldErrors沒有任何異常信息。系統將執行action中的處理方法。

    6.2.5 Action類的執行原理以及順序

    一、類型轉換:類型轉換失敗是在Action調用相應屬性的set方法以前發生的,類型轉換失敗,不影響程序的運行。
    二、set方法:不管類型轉換是否成功,都將執行該屬性的set方法。只不過,類型轉換失敗,會設置該屬性值爲null。
    三、數據驗證:若對於類型轉換失敗的數據,程序中存在爲null的驗證,則會在向fieldErrors集合中加入類型轉換異常信息的同時,將該屬性爲null的驗證信息也加入fieldErrors集合。
    四、Action方法:只有當fieldErrors集合的size爲0,即沒有異常信息時,纔會執行Action方法

    7 攔截器

  • 攔截器是Struts2的一個重要特性。由於Struts2的大多數核心功能都是經過攔截器實現的。攔截器之因此稱之爲「攔截器」,是由於它能夠在執行Action方法以前或者以後攔截下用戶請求,執行一些操做。即在Action方法執行以前或者以後執行,以加強Action方法的功能。
  • 例如,通常狀況下,用戶在打開某個頁面以前,須要先登陸,不然是沒法對資源進行訪問的。這就是權限攔截器。
  • Struts2內置了不少攔截器,每一個攔截器完成相對獨立的功能,多個攔截器的組合體稱之爲攔截器棧。最爲重要的攔截器棧是系統默認的攔截器棧DefaultStack。
  • 攔截器定義在 struts2-core-2.3.24.jar!struts-default.xml中。
攔截器 功能描述
exception 將action拋出的異常映射到結果,這樣就經過重定向來自動處理異常,通常狀況下,應該爲最後一個攔截器
chain 容許當前action可以使用上一個被執行action的屬性,這個攔截器一般要和「chain」結果類型一塊兒使用(<result type="chain" .../>)
conversionError 將轉換錯誤的信息(包括轉換的字符串和參數類型等)存放到action的字段
debugging 當使用Struts2的開發模式時,此攔截器會提供更多的調試信息
fileUpload 此攔截器主要用於文件上傳,它負責解析表單中文件域的內容
i18n 這是支持國際化的攔截器,它負責把所選的語言、區域放入用戶Session中
modelDriven 模型驅動攔截器,當某個Action類實現了ModelDriven接口時,它負責把getModel()方法的結果放入ValueStack中
params 負責解析HTTP請求中的參數,並將參數值設置成Action對應的屬性值
servletConfig 若是某個Action須要直接訪問ServletAPI,就是經過這個攔截器實現,它提供訪問HttpServletRequest和HttpServletResponse的方法,以map的方式訪問
validation 經過執行在xxxAction-validation.xml中定義的校驗器,從而完成數據校驗
workflow 爲action定義默認的工做流,通常跟在validation等其餘攔截器後,當驗證失敗時,不執行action直接重定向到INPUT視圖
  • 對workflow攔截器源碼分析:
  • 默認返回視圖爲Action.INPUT,即"input"
    SSH框架之struts2專題3:Struts2核心
    SSH框架之struts2專題3:Struts2核心

    7.1 普通攔截器定義

  • 一般狀況下,自定義一個普通的攔截器類須要實現攔截器接口INterceptor。該接口中定義了三個方法:
  • public void init():攔截器實例被建立以前被調用。
  • public void destroy():攔截器實例被銷燬以後被調用。
  • public String intercept(ActionInvocation invocation) throws Exception:該方法在Action執行以前被調用,攔截器的附加功能在該方法中實現。執行參數invocation的invoke()方法,就是調用Action方法。
    SSH框架之struts2專題3:Struts2核心

    7.2 攔截器註冊

  • 攔截器類在定義好以後,須要在struts.xml配置文件中註冊,以通知Struts框架。其註冊方式有如下幾種,可是不管哪一種,都須要引入Struts2默認的攔截器棧defaultStack。

    7.2.1 攔截器棧方式註冊

    SSH框架之struts2專題3:Struts2核心

    7.2.2 攔截器方式註冊

    SSH框架之struts2專題3:Struts2核心

    7.2.3 默認攔截器棧方式註冊

  • 若該<package>中的全部Action均要使用該攔截器,則可指定默認的攔截器棧。不過,每一個包只能指定一個默認攔截器。可是,若爲該包中的某個action顯式地指定了某個攔截器,則默認攔截器將不起做用。
    SSH框架之struts2專題3:Struts2核心

    7.3 權限攔截器舉例

  • 只有通過登陸的用戶才能夠訪問Action中的方法,不然,將返回「無權訪問」提示。
  • 本例的登陸,由一個JSP頁面完成,即在該頁面裏將用戶信息放入到session中,也就是說,只要訪問過該頁面,就說明登陸了;沒有訪問過,則爲未登陸用戶。
  • 項目:permission_intercepter

    7.3.1 項目建立

    一、定義login.jsp

    <body>
    <% session.setAttribute("user", "beijing"); %>
    登陸成功!
    </body>

    二、定義logout.jsp

    <!-- 模擬用戶退出 -->
    <%
        session.removeAttribute("user");
    %>
    用戶退出系統!

    三、定義Action

    package com.eason.actions;
    import com.opensymphony.xwork2.ActionContext;
    public class SomeAction {
    public String execute() {
        //可以執行到Action,說明已經經過了攔截器驗證,用戶身份合法
        ActionContext.getContext().getSession().put("message", "歡迎登陸");
        return "success";
    }
    }

    四、定義攔截器

    package com.eason.interceptor;
    import org.eclipse.jdt.internal.compiler.ast.Invocation;
    import com.opensymphony.xwork2.ActionContext;
    import com.opensymphony.xwork2.ActionInvocation;
    import com.opensymphony.xwork2.interceptor.Interceptor;
    
    public class PermissionInterceptor implements Interceptor {
        public void destroy() {
        }
        public void init() {
        }
    
        @Override
        public String intercept(ActionInvocation invocation) throws Exception {
            String user = (String) ActionContext.getContext().getSession().get("user");
            if("beijing".equals(user)) {
                return invocation.invoke();
            }
            ActionContext.getContext().getSession().put("message", "您未登陸");
            return "message";
        }
    }

    五、配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
    "http://struts.apache.org/dtds/struts-2.3.dtd">
    <struts>
    <package name="demo" namespace="/test" extends="struts-default">
        <interceptors>
            <interceptor name="permission" class="com.eason.interceptor.PermissionInterceptor"></interceptor>
            <interceptor-stack name="permissionStack">
                <interceptor-ref name="permission"></interceptor-ref>
                <interceptor-ref name="defaultStack"></interceptor-ref>
            </interceptor-stack>
        </interceptors>
    
        <action name="some" class="com.eason.actions.SomeAction">
            <interceptor-ref name="permissionStack"></interceptor-ref>
            <result>/success.jsp</result>
            <result name="message">/message.jsp</result>
        </action>
    </package>
    </struts>

    六、定義success.jsp

    <body>
    進入系統!
    </body>

    七、定義message.jsp

    <body>
    message = ${message }
    </body>

    7.3.2 項目測試

    一、在地址欄上直接提交some.action請求:
    SSH框架之struts2專題3:Struts2核心
    二、訪問 login.jsp,進行用戶登陸:
    SSH框架之struts2專題3:Struts2核心
    三、再次提交 some.action 請求:
    SSH框架之struts2專題3:Struts2核心
    四、訪問 logout.jsp,進行用戶退出:
    SSH框架之struts2專題3:Struts2核心
    五、第三次提交 some.action 請求:
    SSH框架之struts2專題3:Struts2核心

    7.4 方法過濾攔截器

  • 直接實現Interceptor接口的過濾器存在一個問題:當將攔截器註冊爲<package/>中默認的攔截器棧<default-interceptor-ref/>時,會對該<package/>中全部<action/>指定的method進行攔截。此時,可以使用Interceptor接口的實現類MethodFilterInterceptor--方法過濾攔截器對指定方法進行過濾。
  • 自定義方法過濾器,可繼承MethodFilterInterceptor實現類,重寫其方法doInterceptor()。workflow攔截器就是繼承自該實現類:
    SSH框架之struts2專題3:Struts2核心
  • 項目:permission_intercepter2
    一、定義Action:
    SSH框架之struts2專題3:Struts2核心
    二、定義攔截器類
    SSH框架之struts2專題3:Struts2核心
    三、修改配置文件
    SSH框架之struts2專題3:Struts2核心
  • MethodFilterInterceptor攔截器有兩個參數可進行設置,用於指定或者排除要攔截的方法名。這兩個屬性不能同時進行設置:
    SSH框架之struts2專題3:Struts2核心

    8 國際化

    8.1 國際化基礎

    8.1.1 獲取系統支持的語言和國家簡稱

  • 進行國際化編程和調試,須要瞭解系統支持的語言和國家簡稱,以及瀏覽器切換語言的位置。通常都是在瀏覽器的「工具 -> Internect 選項 ->語言"中可查看到。下面以360瀏覽器爲例查看。
    SSH框架之struts2專題3:Struts2核心
  • 另外,在Java代碼中也能夠經過java.util.locale類中的靜態方法獲取Java支持的語言和國家簡稱。
    SSH框架之struts2專題3:Struts2核心

    8.1.2 Struts2的i18n攔截器

  • Struts2的默認攔截器中包含了i18n攔截器。而i18n攔截器中包含了一個屬性request_locale,專門用於設置瀏覽器的語言。
    SSH框架之struts2專題3:Struts2核心
  • 經過設置i18n攔截器的request_locale屬性,可對被攔截Action所要轉向頁面的瀏覽器的語言進行設置。
    SSH框架之struts2專題3:Struts2核心

    8.2 全局範圍資源文件

  • 所謂全局範圍資源文件是指,整個應用中的全部文件都可訪問的資源文件。其命名要遵循如下格式:baseName_language_country.properties
  • 其中baseName是資源文件的基本名,咱們能夠自定義。可是language和country必須是Java支持的語言和國家簡稱。例如, login_en_US.properties。
  • 對於全局資源文件,須要注意如下幾點:
    一、國家簡稱必須爲大寫;二、對於同一內容進行解釋的資源文件,其基本名必須相同;三、資源文件須要在struts.xml中註冊,註冊其位置與基本名。

    8.2.1 JSP 中普通文本的國際化

  • 在 JSP 頁面中,普通文本使用 Struts2 標籤<s:text name=「」/>輸出國際化信息。其中 name的值爲資源文件中指定的 key。如:<s:text name=「formhead」/>將會在此顯示「登陸表單」字樣。

    8.2.2 JSP 中表單元素的國際化

  • 在 JSP 頁面中,使用 Struts2 表單元素標籤,經過 key 屬性指定屬性文件中的 key,如:<s:textfield name=「userName」 key=「uname」/>,標籤名稱將顯示「用戶名」。<s:submit key=」submit」/>,提交按鍵上將顯示「登陸」。
    SSH框架之struts2專題3:Struts2核心

    8.2.3 Action類中文本的國際化

  • Action類中文本的國際化,可使用ActionSupport類的getText()方法,該方法的參數用於指定屬性文件中的key。如,String message = this.getText("message");,此時message的實際值爲「登陸成功「字符串。因此,Action類要繼承ActionSupport類。

    8.2.4 資源文件中的佔位符

  • 資源文件的value值中也能夠包含動態參數,即在程序運行時才肯定資源文件中value的值。此時,資源文件的value值中的動態參數將以佔位符的形式出現,如{0}、{1}等。
  • 若在JSP頁面中,參數的值可經過爲<s:text/>添加<s:param/>來設置。
    SSH框架之struts2專題3:Struts2核心
  • 若在Action類中,則經過ActionSupport類的帶兩個參數的getText()進行賦值。要求第二個參數爲String[]。
    SSH框架之struts2專題3:Struts2核心

    8.3 包範圍資源文件

  • 在一個大型應用中,整個應用有大量的內容須要實現國際化。若均放入全局資源文件中,則會使得全局資源文件過於龐大,因此能夠針對不一樣模塊、不一樣的action來組織國際化文件。
  • 在要使用該資源文件的java的包下,放置按以下格式命名的資源文件:package_language_country.properties。
  • 其中,package爲固定寫法。處於該包以及子包下的全部action均可以訪問該資源。當查找指定key的消息時,系統會先從package資源文件查找,當找不到對應的key時,纔會從全局資源文件中尋找。即包範圍資源文件的優先級高於全局資源文件。
  • 注意,非經Action跳轉而至的JSP頁面中讀取的是全局資源文件中的內容。而經由Action跳轉而來的JSP頁面,其讀取的爲跳轉而來的Action所在包的資源文件。

    8.4 Action範圍資源文件

  • 能夠單獨爲某個action指定資源文件。只須要在Action類所在的包中放置命名格式以下的資源文件:ActionClassName_language_country.properties。
  • 其中ActionClassName爲action類的簡單名稱。當查找指定key的消息時,系統會先從action範圍資源文件查找,若是沒有找到對應的key,接着會沿當前包往上查基本名爲package的資源文件,一直找到最頂層包。若是尚未找到對應的key,最後會從全局資源文件中尋找。

    8.5 JSP中訪問指定資源文件

  • 因爲JSP頁面所訪問資源文件不是由JSP頁面自己決定看,要麼默認訪問全局資源文件,要麼訪問其跳轉來的Action中的資源文件。而這將引起項目在進行分工協做時的麻煩。Struts2的<s:i18n/>標籤可讓JSP訪問指定資源文件。
  • <s:i18n/>具備一個name屬性,用於指定所要訪問資源文件的路徑以及基本名。
    SSH框架之struts2專題3:Struts2核心

    9 文件上傳

  • Struts2是經過攔截器實現文件上傳的,而默認攔截器中包含了文件上傳攔截器,故表單經過Struts2可直接將文件上傳。其底層是經過apache的commons-fileupload完成的。
    SSH框架之struts2專題3:Struts2核心
    SSH框架之struts2專題3:Struts2核心
  • 若要實現文件上傳功能,表單的enctype屬性值與method屬性值必需要以下設置:
    SSH框架之struts2專題3:Struts2核心

    9.1 上傳單個文件

  • 舉例:fileUploadSingle
    一、新建index.jsp:

    <form action="test/upload.action" method="post" enctype="multipart/form-data">
        文件:<input type="file" name="img"/><br/>
        <input type="submit" value="上傳"/>
    </form>

    二、新建Action類

    package com.eason.struts.fileupload;
    
    import java.io.File;
    import java.io.IOException;
    import org.apache.commons.io.FileUtils;
    import org.apache.struts2.ServletActionContext;
    
    public class UploadAction {
        private File img;
        private String imgFileName;
    
        public File getImg() {
            return img;
        }
        public void setImg(File img) {
            this.img = img;
        }
        public String getImgFileName() {
            return imgFileName;
        }
        public void setImgFileName(String imgFileName) {
            this.imgFileName = imgFileName;
        }
    
        public String execute() throws IOException {
            if(img != null) {
                String path = ServletActionContext.getServletContext().getRealPath("/images");
                File file = new File(path, imgFileName);
                FileUtils.copyFile(img, file);
                return "success";
            }
            return "fail";
        }
    }

    三、struts.xml中的配置:

    <package name="demo" namespace="/test" extends="struts-default">
        <action name="upload" class="com.eason.struts.fileupload.UploadAction">
            <result>/success.jsp</result>
            <result name="fail">/fail.jsp</result>                        
        </action>
    </package>

    四、定義success.jsp和fail.jsp:

    <body>
    上傳成功!
    </body>
    <body>
    上傳失敗!
    </body>
  • <input type="file" name="img"/>中的name屬性值爲img,因此Action類中必須定義File img;和String imgFileName;兩屬性用以接收文件和文件名。
  • 注意,在Action中想經過獲取文件大小來控制文件上傳是不行的。由於文件上傳攔截器是在Action以前執行的,即執行到Action時,文件上傳工做已經完成。即便超過限制大小,拋出異常,也是已經拋出異常後才執行到Action的。

    9.2 上傳多個文件

  • 與上傳單個文件相比較,發生了以下幾個變化:
    一、提交表單中出現多個文件上傳欄,這多個的name屬性名必須徹底相同。
    二、Action中文件再也不爲File類型,而是File類型的數組或者是List。固然,文件名也爲相應的數組或者是List。
    三、Action方法須要遍歷這些數組來上傳這些文件。
  • 舉例:fileuploadMultiple
  • 拷貝fileuploadSingle,只須要修改上傳頁面以及Action便可。
    一、修改上傳頁面:

    <form action="test/upload.action" method="post" enctype="multipart/form-data">
        文件1:<input type="file" name="img"/><br/>
        文件2:<input type="file" name="img"/><br/>
        文件3:<input type="file" name="img"/><br/>
        <input type="submit" value="上傳"/>
    </form>

    二、修改 Action:

    package com.eason.struts.fileupload;
    import java.io.File;
    import java.io.IOException;
    import org.apache.commons.io.FileUtils;
    import org.apache.struts2.ServletActionContext;
    
    public class UploadAction {
        private File[] imgs;
        private String[] imgsFileName;
    
        public File[] getImgs() {
            return imgs;
        }
        public void setImgs(File[] imgs) {
            this.imgs = imgs;
        }
        public String[] getImgsFileName() {
            return imgsFileName;
        }
        public void setImgsFileName(String[] imgsFileName) {
            this.imgsFileName = imgsFileName;
        }
    
        public String execute() throws IOException {
            if(imgs != null) {
                for(int i = 0; i < imgs.length; i++) {
                    String path = ServletActionContext.getServletContext().getRealPath("/images");
                    File file = new File(path, imgsFileName[i]);
                    FileUtils.copyFile(imgs[i], file);
                }
                return "success";
            }
            return "fail";
        }
    }

    9.3 設置上傳文件大小的最高限

  • Struts2默認上傳文件的大小是不能超過2M的。若要想上傳大於2M的內容,則須要Struts.xml中增長對上傳最大值的常量設置。這是當前系統的上傳文件大小的最高限。
    SSH框架之struts2專題3:Struts2核心

    9.4 限制上傳文件的擴展名

  • 查看文件上傳攔截器FileUploadInterceptor源碼,能夠看到其有一個allowedExtensions屬性,該屬性可用於限制上傳文件的擴展名。
    SSH框架之struts2專題3:Struts2核心
  • 查看setAllowedExtendsions()方法的源碼,其調用了方法commaDelimitedStringToSet()即逗號分隔字符串到Set集合方法,用於解析出使用逗號分隔的多個擴展名。
    SSH框架之struts2專題3:Struts2核心
  • allowedExtensions屬性的用法以下所示:
    SSH框架之struts2專題3:Struts2核心

    10 文件下載

  • 服務端向客戶端瀏覽器發送文件時,若是是瀏覽器支持的文件類型,通常會默認使用瀏覽器打開,好比txt,jpg等,會直接在瀏覽器中顯示;若是須要用戶以附件的形式保存,則稱之爲文件下載。
  • 若是須要向瀏覽器提供文件下載功能,則須要設置HTTP響應頭的Content-Disposition屬性,即內容配置屬性值爲attachment(附件)。
  • Action類中須要提供兩個屬性,一個爲文件輸入流,用於指定服務器向客戶端所提供下載的文件資源;一個爲文件名,即用戶要下載的資源文件名。配置文件中Action的result類型,即type屬性應該設置爲stream。
  • 舉例:download
    一、在頁面提供文件下載連接,即用戶在瀏覽器上提交文件下載請求的位置:

    <body>
    <a rel="nofollow" href="test/download.action">美圖下載</a>
    </body>

    二、定義Aciton類:

    package com.eason.struts2.action;
    import java.io.InputStream;
    import org.apache.struts2.ServletActionContext;
    public class DownloadAction {
        //服務器本地提供下載資源的輸入流
        private InputStream is;
        //爲用戶所提供的下載資源的文件名
        private String fileName;
    
        public void setIs(InputStream is) {
            this.is = is;
        }
        public void setFileName(String fileName) {
            this.fileName = fileName;
        }
        public InputStream getIs() {
            return is;
        }
        public String getFileName() {
            return fileName;
        }
    
        public String execute() {
            //指定要下載的文件名
            fileName = "timg.jpg";
            is = ServletActionContext.getServletContext().getResourceAsStream("/images/" + fileName);
            //在輸入流後修改fileName的值,即爲用戶下載到客戶端後的文件名
            fileName = "beauty.jpg";
            return "success";
        }   
    }

    三、設 置 action 的 視 圖 類 型 爲 stream , 並 爲 其 設 置 兩 個 參 數 inputName 與
    contentDisposition:

    <package name="demo" namespace="/test" extends="struts-default">
        <action name="download" class="com.eason.struts2.action.DownloadAction">
            <result type="stream">
                <param name="inputName">is</param>
                <param name="contentDisposition">
                    attachment;filename=${fileName}
                </param>
            </result>
        </action>
    </package>
  • 查看struts-default.xml,能夠看到Stream返回類型對應的類爲 StreamResult。
    SSH框架之struts2專題3:Struts2核心
  • 查看StreamResult源碼,相關屬性以下:
    SSH框架之struts2專題3:Struts2核心
  • DEFAULT_PARAM:配置文件中<param/>標籤name屬性的默認值。
  • contentType:文件的MIME類型,如image/jpeg。不管下載的文件是什麼類型,客戶端看到的均會是指定類型文件的擴展名,如均是jpg。
  • contentLength:對服務器提供的下載文件大小的上限的限制。單位字節。當下載文件大於此大小時,下載仍會繼承。可是,下載不全,只要規定的大小。
  • contentDisposition:下載文件的顯示形式。默認爲「inline」,即在瀏覽器上直接顯示。若要以附件形式展現給客戶端,則其值須要設置爲attachment,並經過filename指定其下載後的名稱。
  • inputName:輸入流的名稱,默認爲inputStream。若Action中的inputStream對象的名字爲inputStream,此時,<param name=」inputName」>inputStream</param>省略不寫也可。由於DEFAULT_PARAM指定了默認的param爲inputName,而默認的InputStream對象又爲inputStream。省略不寫,則均會使用默認值。
  • bufferSize:提供下載是可使用的緩存大小。
  • allowCaching:提供下載服務時是否容許使用緩存。
    四、此時程序部署後就能夠完成向客戶端提供下載功能,可是有個問題,當指定文件下載到客戶端的名稱爲中文時,瀏覽器下載會出現文件名稱的亂碼。解決方式以下:
    public String execute() throws Exception {
        //指定要下載的文件名
        fileName = "timg.jpg";
        is = ServletActionContext.getServletContext().getResourceAsStream("/images/" + fileName);
        //在輸入流後修改fileName的值,即爲用戶下載到客戶端後的文件名
        fileName = "美女.jpg";
        fileName = new String(fileName.getBytes("utf-8"), "ISO8859-1");
        return "success";
    }
  • 亂碼是如何產生的?爲何使用new String(fileName.getBytes("utf-8"), "ISO8859-1")後,就解決亂碼問題?
  • 此時的亂碼之因此會產生,是由於Http header報頭要求其內容必須爲ISO8859-1編碼,而ISO8859-1編碼不支持漢字。
  • 使用以上方式之因此能夠解決亂碼問題,是由於:
    一、fileName.getBytes("utf-8)的做用是,將fileName按照utf-8進行編碼,並將編碼結果存放到字節數組中。utf-8編碼中文後爲3個字節。
    二、new String(fileName.getBytes("utf-8"), "ISO8859-1")的做用是,將編碼後的3個字節解碼爲ISO8859-1串,即相似%3A56%59DC這樣的串。而這樣的串正是瀏覽器所須要的,Httpheader報頭中數據的編碼集爲ISO8859-1。當數據傳到瀏覽器端後,瀏覽器會自動將數據按照瀏覽器的字符集進行編碼,若瀏覽器的爲utf-8,則會正常顯示漢字。

    11 防止表單重複提交

  • 在實際應用中,因爲網速等緣由,用戶在提交過表單後,可能很長時間內服務器未給出任何響應。此時,用戶就可能會重複對錶單進行提交。
  • 或者是表單提交後,服務器也給出了響應,但用戶在響應頁面不斷點擊「刷新」按鈕進行頁面刷新。此時,用戶也是在對錶單進行重複提交。

    11.1 令牌機制

  • Struts2 中,使用令牌機制防止表單自動提交。
  • 所謂令牌機制是指,當用戶提交頁面資源請求後,服務器會產生一個隨機的字符串,該字符串即爲令牌。併爲該字符串保留兩個副本:一個保留在服務器,一個隨着響應返回給客戶端瀏覽器。
  • 當用戶在頁面中進行第一次提交時,會將副本字符串與服務器中的副本字符串進行比較。其比較結果必定是相同的,由於這是第一次提交。
  • 當比較結果相同後,服務器會將其保留的副本數據修改。但此修改並不通知(影響)瀏
    覽器端的原副本。
  • 當用戶在頁面中進行重複提交時,在發出的請求中仍然攜帶有原副本字符串,服務器仍然會將該副本與本身的令牌字符串進行比較。但服務器端的令牌字符串已經發生改變,因此比較結果必定不一樣。這就說明不是第一次提交該請求了,是重複提交了。

    11.2 防止表單重複提交

    一、使用<s:token />標籤,該標籤應放在<form/>表單中,以便在請求提交時,將令牌字符
    串一塊兒提交。
    二、在定義 Action 時,要求 Action 必須繼承 ActionSupport 類。以便在重複提交發生時會
    返回」invalide.token」字符串。
    三、Struts2 配置文件的<action/>中須要定義名稱爲」invalide.token」的視圖。
    四、Struts2 配置文件的<action/>中須要使用 token 攔截器。

    11.3 防止重複提交的步驟

  • 舉例:token
    一、在表單中加入<s:token/>
    <form action="test/login.action" method="post">
        <s:token/>
        用戶:<input type="text" name="username"/><br/>
        密碼:<input type="password" name="password"/><br/>
        <input type="submit" value="登陸">
    </form>
  • 當服務器發現用戶提交的頁面訪問請求,所請求的頁面中包含<s:token/>標籤,則會生
    成令牌字符串,併爲令牌建立兩個副本。一個放在服務器端,一個在給出的對用戶頁面請求響應的頁面,以隱藏域的方式發送給客戶端瀏覽器。
    SSH框架之struts2專題3:Struts2核心
    二、Action類要繼承自ActionSupport類

    package com.eason.struts.action;
    import com.opensymphony.xwork2.ActionSupport;
    public class LoginAction extends ActionSupport{
    private String username;
    private String password;
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    
    public String execute() {
        return "success";
    }
    }

    三、在action配置文件中加入token攔截器以及重複提交視圖配置。注意,在加入token攔截器以前,不要忘記先要將核心攔截器棧加入,不然,Struts2的核心功能將沒法使用。

    <package name="demo" namespace="/test" extends="struts-default">
        <action name="login" class="com.eason.struts.action.LoginAction">
            <interceptor-ref name="token"></interceptor-ref>
            <interceptor-ref name="defaultStack"></interceptor-ref>
            <result>/success.jsp</result>
            <result name="invalid.token">/message.jsp</result>
        </action>
    </package>

    四、定義成功頁面和重複提交提示頁面。

    <body>
    提交成功!
    </body>
    <body>
    發生了重複提交!
    </body>
相關文章
相關標籤/搜索