1. 程式人生 > >深入分析JavaWeb 45 -- Struts2封裝請求引數與型別轉換

深入分析JavaWeb 45 -- Struts2封裝請求引數與型別轉換

作為MVC框架,必須要負責解析HTTP請求引數,並將其封裝到Model物件中,Struts2提供了非常強大的型別轉換機制用於請求資料 到 model物件的封裝。

1、Struts2 提供三種資料封裝的方式

  • Action 本身作為model物件,通過成員setter封裝

這裡寫圖片描述

  • 建立獨立model物件,頁面通過ognl表示式封裝

這裡寫圖片描述

  • 使用ModelDriven介面,對請求資料進行封裝

這裡寫圖片描述

1. 方式一:在動作類中成員變數給予初始值。

在配置檔案中struts.xml中

<packagename="p1"extends="struts-default">
        <
actionname="action1"class="com.itheima.actions.PersonAction">
<result>/result.jsp</result> </action> </package>

編寫result.jsp

  <body>
    ${name}
  </body>

編寫實現類PersonAction,重寫excute()方法

package com.itheima.actions;

import com.opensymphony.xwork2.ActionSupport;

public
classPersonActionextendsActionSupport { private String name = "劉小晨"; private String password; private int age; public String getName() { return name; } public void setName(String name) { System.out.println("呼叫了setName方法"); this.name = name; } public
String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String execute() throws Exception { System.out.println(name+":"+password+":"+age); return super.execute(); } }

執行結果為:劉小晨

這種在類的成員變數時,就賦值的方法,亦可以在配置檔案中注入動作類的引數值(靜態引數設定),Action 本身作為model物件,通過成員setter封裝(一個名字為params的攔截器乾的)

重新配置struts.xml如下:

  <packagename="p1"extends="struts-default">
        <actionname="action1"class="com.itheima.actions.PersonAction">
            <!-- 給動作類的例項注入引數值:相當於呼叫PersonAction.setName("王衛星") -->
            <paramname="name">王衛星</param>
            <result>/result.jsp</result>
        </action>
    </package>

執行結果為:王衛星

實際上是由一個叫做staticParams的攔截器做的,可以檢視 struts-default.xml配置檔案,如果將staticParams這個攔截器刪除,則得不到想要的結果

這裡寫圖片描述

此外還有動態引數注入,同樣用動作類作為model物件。

編寫result.jsp

<body>
    <formaction="${pageContext.request.contextPath}/action1"method="post">
        使用者名稱:<inputtype="text"name="name"/><br/>
        密碼:<inputtype="text"name="password"/><br/>
        年齡:<inputtype="text"name="age"/><br/>
        <inputtype="submit"value="儲存"/>
    </form>
  </body>

配置檔案和動作實現類不變,這裡要注意: 表單的欄位輸入域的name,password, age取值要和動作類的寫屬性名稱一致。

執行結果:表單中name輸入的結果。

動態引數的注入也是由一個攔截器來做的:params

2、方式二:動作類和模型分開(我們以表單請求引數為例)

寫配置檔案struts.xml

<actionname="saveStudent"class="com.itheima.actions.StudentAction"></action>

編寫動作類StudentAction

package com.itheima.actions;

import com.itheima.domain.Student;
import com.opensymphony.xwork2.ActionSupport;

public classStudentActionextendsActionSupport {
    private Student student = new Student();

    public Student getStudent() {
        System.out.println("呼叫了getStudent方法");
        return student;
    }

    public void setStudent(Student student) {
        System.out.println("呼叫了setStudent方法");
        this.student = student;
    }

    public String execute() throws Exception {
        System.out.println(student);
        return NONE;
    }

}

編寫模型類Student.java

package com.itheima.domain;

import java.io.Serializable;

public classStudentimplementsSerializable {
    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;
    }
    @Override
    public String toString() {
        return "Student [name=" + name + ", age=" + age + "]";
    }

}

編寫訪問的頁面addStudent.jsp

  <body>
    <formaction="${pageContext.request.contextPath}/saveStudent"method="post">
        使用者名稱:<inputtype="text"name="student.name"/><br/>
        年齡:<inputtype="text"name="student.age"/><br/>
        <inputtype="submit"value="儲存"/>
    </form>
  </body>

這樣上述就將模型(Student)和動作類(StudentAction)分開了,在頁面中,我們可以用student.name和student.age來封裝請求引數了。

主要原理如下:

  1. 框架建立了一個Student的例項,通過呼叫setStudent(Student s),傳遞物件
  2. 框架再呼叫StudentAction的getStudent(),方法,得到剛剛建立的物件
  3. 緊接著,呼叫student例項的setName和setAge方法,設定值。

3. 方式三:模型驅動(面試)(與後面ValueStack值棧有關)

編寫struts.xml配置檔案

<action name="saveCustomer" class="com.itheima.actions.CustomerAction"/>

編寫CustomerAction的動作類

package com.itheima.actions;

import com.itheima.domain.Customer;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;
//使用模型驅動:
public classCustomerActionextendsActionSupportimplementsModelDriven<Customer>{
    private Customer customer = new Customer();//這裡一定要new()出一個實體類。

    public Customer getCustomer() {
        return customer;
    }

    public void setCustomer(Customer customer) {
        this.customer = customer;
    }

    @Override
    public String execute() throws Exception {
        System.out.println(customer);
        return NONE;
    }

    public Customer getModel() {
        return customer;
    }

}

編寫實體類Customer.java

package com.itheima.domain;

import java.io.Serializable;

public classCustomerimplementsSerializable {
    private String name;
    private String city;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getCity() {
        return city;
    }
    public void setCity(String city) {
        this.city = city;
    }
    @Override
    public String toString() {
        return "Customer [name=" + name + ", city=" + city + "]";
    }

}

編寫addCustomer.jsp頁面

  <body>
    <formaction="${pageContext.request.contextPath}/saveCustomer.action"method="post">
        使用者名稱:<inputtype="text"name="name"/><br/>
        城市:<inputtype="text"name="city"/><br/>
        <inputtype="submit"value="儲存"/>
    </form>
  </body>

這裡可以直接寫name和city來封裝請求引數,而不需要些customer.name和customer.city。

注:模型驅動實際上是由一個攔截器來做的。modelDriven攔截器來做。把getModel方法返回的物件,壓入一個叫做ValueStack的棧頂。struts框架就會根據表單的name屬性,呼叫對應棧頂物件的setter方法,從而把請求引數封裝進來。可以看原始碼:
注意這一定要new出來! 否則框架呼叫CustomerAction的getCustomer()方法,得到返回值為null。

2、 封裝資料到集合型別中

Struts2 還允許填充 Collection 裡的物件, 這常見於需要快速錄入批量資料的場合

  • 型別轉換與Collection配合使用

這裡寫圖片描述

  • 型別轉換與Map配合使用

這裡寫圖片描述

例項:

編寫配置struts.xml

<action name="collectionAction1" class="com.itheima.actions.CollectionAction"/>
<action name="collectionAction2" class="com.itheima.actions.CollectionAction"/>
<action name="collectionAction3" class="com.itheima.actions.CollectionAction"/>

編寫動作類CollectionAction

package com.itheima.actions;

import java.util.Arrays;
import java.util.Collection;
import java.util.Map;

import com.itheima.domain.Employee;
import com.opensymphony.xwork2.ActionSupport;

public classCollectionActionextendsActionSupport {
    private String[] hobby;//陣列
    private Collection<Employee> employees;//集合

    private Map<String, Employee> emps;//map

    public String[] getHobby() {
        return hobby;
    }

    public void setHobby(String[] hobby) {
        this.hobby = hobby;
    }

    public Collection<Employee> getEmployees() {
        return employees;
    }

    public void setEmployees(Collection<Employee> employees) {
        this.employees = employees;
    }

    public Map<String, Employee> getEmps() {
        return emps;
    }

    public void setEmps(Map<String, Employee> emps) {
        this.emps = emps;
    }

    public String execute() throws Exception {
//      System.out.println(Arrays.asList(hobby));
//      System.out.println(employees);
        if(emps!=null){
            for(Map.Entry<String, Employee> me:emps.entrySet()){
                System.out.println(me.getKey()+":"+me.getValue());
            }
        }
        return NONE;
    }

}

編寫實體類Employee

package com.itheima.domain;

import java.io.Serializable;

public classEmployeeimplementsSerializable {
    private String name;
    private float salary;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public float getSalary() {
        return salary;
    }
    public void setSalary(float salary) {
        this.salary = salary;
    }
    @Override
    public String toString() {
        return "Employee [name=" + name + ", salary=" + salary + "]";
    }

}

編寫collectionDemo.jsp

<body>
    <form action="${pageContext.request.contextPath}/collectionAction1" method="post">
        愛好:
            <input type="checkbox" name="hobby" value="吃飯"/>吃飯
            <input type="checkbox" name="hobby" value="睡覺"/>睡覺
            <input type="checkbox" name="hobby" value="學java"/>學java
            <input type="submit" value="儲存"/>
    </form>
    <hr/>
    <h2>批量新增員工資訊</h2>
    <form action="${pageContext.request.contextPath}/collectionAction2" method="post">
        員工1:姓名:<input type="text" name="employees[0].name"/>薪水:<input type="text" name="employees[0].salary"/><br/>
        員工2:姓名:<input type="text" name="employees[1].name"/>薪水:<input type="text" name="employees[1].salary"/><br/>
        員工3:姓名:<input type="text" name="employees[2].name"/>薪水:<input type="text" name="employees[2].salary"/><br/>
        <input type="submit" value="儲存"/>
    </form>
    <h2>向Map中新增內容</h2>
    <form action="${pageContext.request.contextPath}/collectionAction3" method="post">
        員工1:姓名:<input type="text" name="emps['e1'].name"/>薪水:<input type="text" name="emps['e1'].salary"/><br/>
        員工2:姓名:<input type="text" name="emps.e2.name"/>薪水:<input type="text" name="emps.e2.salary"/><br/>
        員工3:姓名:<input type="text" name="emps.e3.name"/>薪水:<input type="text" name="emps.e3.salary"/><br/>
        <input type="submit" value="儲存"/>
    </form>
  </body>

3、型別轉換

對於大部分常用型別,開發者根本無需建立自己的轉換器,Struts2內建了常見資料型別多種轉換器,8大基本型別自動轉換。
- boolean 和 Boolean
- char和 Character
- int 和 Integer
- long 和 Long
- float 和 Float
- double 和 Double
- Date 可以接收 yyyy-MM-dd格式字串,java.util.Date<——–>String(中國:Struts2預設按照yyyy-MM-dd本地格式進行自動轉換)
- 陣列 可以將多個同名引數,轉換到陣列中
- 集合 支援將資料儲存到 List 或者 Map 集合

總結:在使用Struts2時,基本上不用寫任何型別轉換器。內建的完全夠用。

但是因為使用者介面傳來的資料都是String:String—->其他型別,當一些需求是顯示或者是資料回顯:其他型別—–>String時,我們就要做自定義型別轉換了。

這裡寫圖片描述

可以看一下原始碼

這裡寫圖片描述

這裡寫圖片描述

這裡寫圖片描述

從原始碼可以看出,繼承org.apache.struts2.util.StrutsTypeConverter(最簡單和方便)

1、自定義型別轉換器

String—————–>java.util.Date MM/dd/yyyy—–>能轉換
java.util.Date———>String MM/dd/yyyy

步驟:

1. 編寫一個類直接或間接實現:
com.opensymphony.xwork2.conversion.TypeConverter介面。
也可以選擇繼承:
com.opensymphony.xwork2.conversion.impl.DefaultTypeConverter

我們選擇繼承org.apache.struts2.util.StrutsTypeConverter,並覆蓋掉如下方法
public Object convertValue(Object value, Class toType)

假如在動作類HelloWorldAction 中有一個時間引數createtime

import java.util.Date;
public class HelloWorldAction {
    private Date createtime;

    public Date getCreatetime() {
        return createtime;
    }

    public void setCreatetime(Date createtime) {
        this.createtime = createtime;
    }
}

自定義我們的轉換器MyDateConvertor ,覆蓋convertFromString()和convertToString()方法。

package com.itheima.convertor;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;

import org.apache.struts2.util.StrutsTypeConverter;
//日期轉換器:
/*
 * String :12/31/2001 ---->Date
 * Date---------->String:12/31/2001
 */
public classMyDateConvertorextendsStrutsTypeConverter {
    private DateFormat df = new SimpleDateFormat("MM/dd/yyyy");
    //從字串轉換成日期
    public Object convertFromString(Map context, String[] values, Class toClass) {
        if(toClass==Date.class){
            String value = values[0];//獲取使用者輸入的引數值12/31/2001
            try {
                return df.parse(value);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    //日期轉換成字串
    public String convertToString(Map context, Object o) {
        if(o instanceof Date){
            Date d = (Date)o;
            return df.format(d);
        }
        return null;
    }

}

2.註冊型別轉換器

  • 區域性型別轉換器:為某個動作類服務的(特服)

在Action類所在的包下放置ActionClassName-conversion.properties檔案,ActionClassName是Action的類名,後面的-conversion.properties是固定寫法,對於本例而言,檔案的名稱應為HelloWorldAction-conversion.properties 。在properties檔案中的內容為:

屬性名稱=型別轉換器的全類名

對於本例而言, HelloWorldAction-conversion.properties檔案中的內容為:

createtime= cn.itcast.conversion.DateConverter

  • 全域性型別轉換器:為所有的動作類服務的
    在WEB-INF\classes目錄下(在src下)建立一個固定名稱的配置檔案:xwork-conversion.properties

    對於本例而言, xwork-conversion.properties檔案中的內容為: