深入分析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來封裝請求引數了。
主要原理如下:
- 框架建立了一個Student的例項,通過呼叫setStudent(Student s),傳遞物件
- 框架再呼叫StudentAction的getStudent(),方法,得到剛剛建立的物件
- 緊接著,呼叫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檔案中的內容為: