第七章 Type Convertion

為什么會有類型轉換?

HTTP協 議中傳遞的任何內容都是String類型的,所以一旦我們在服務器上需要一個非String類型的對象,例如:int或者Date,那么我們就需要在收到 HTTP請求的數據的時候,首先將String類型的數據變換為我們需要的對應類型的數據,之后再使用。這個過程就是類型轉換

類型轉換在Struts2中是透明的,即Struts2內置了類型轉換機制。

 
轉換原理:
以一個例子來說明如何使用Struts2內置的類型轉換功能。加入我們希望用戶在畫面上輸入一個字符形式的坐標點,例如(33,2)而我們希望在程序中得到一個Point(33, 2)的類型與之對應。
要想達到上面的功能我們需要一個名字位:ActionName-conversion.properties的文件,在文件中定義Action中的屬性和畫面字段之間的轉換關系。例如:

point = com.jpleasure.convertor.PointConverter

也 就是說畫面一個叫做point的項目(input類型,name為point)提交到服務器上之后,在向Action中的point屬性賦值之前需要使用 PointConverter將字符串轉換為Point類,在Action中的point屬性向畫面顯示的時候需要使用PointConverter將 Point類轉換為字符串類型。
其中PointConverter需要實現ognl.TypeConverter接口。TypeConverter有兩個接口,一個負責將字符串轉變為對象類型,另一個負責將對象類型轉換為字符串類型,分別對應著內容的提交和顯示。
有些時候我們希望所有的Point類在默認的情況下使用PointConverter來轉換,這時候我們需要定義全局的Converter類。這可以在xwork-conversion.properties文件中定義,例如:
com.jpleasure.Point = com.jpleasure.convertor.PointConverter
在Struts2中提供了一個TypeConverter接口的默認實現:
org.apache.struts2.action.util.StrutsTypeConverter
這個類有兩個默認的抽象轉換方法和performFallbackConversion,performFallbackConversion方法負責處理類型轉換出錯的處理。
在 自定義TypeConverter的時候,可以實現TypeConverter接口,之后編寫TypeConverter的轉換方法,也可以從 StrutsTypeConverter繼承而來,StrutsTypeConverter本身實現了TypeConverter接口,并且實現了基本的 轉換方法。
內建的轉換:
Struts2內建了對以下類型的轉換的支持:
String
boolean / Boolean
char / Character
int / Integer, float / Float, long / Long, double / Double
dates - 使用HTTP 請求對應地域(Locale)的SHORT形式轉換字符串和日期類型。
arrays -每一個字符串內容可以被轉換為不同的對象
collections - 轉換為Collection類型,默認為ArrayList類型,其中包含String類型。
對于Array類型和Collection類型,需要對其中的每一個元素進行單獨的轉換。
自定義TypeConverter:
使用如下的代碼自定義需要的TypeConverter
public class MyConverter extends StrutsTypeConverter {
    public Object convertFromString(Map context, String[] values, Class toClass) {
       .....
    }
    public String convertToString(Map context, Object o) {
       .....
    }
 }
為了讓Struts2框架發現類型轉換的錯誤,需要在出錯的情況下在上述的兩個方法中拋出XWorkException或者TypeConversionException。

我們使用一個例子來展現如何實現TypeConvertor類型:

// Point 類
package com.jpleasure.conversion;

/**
 * Created by IntelliJ IDEA.
 * User: ma.zhao@dl.cn
 * Date: 2007/09/04
 * Time: 12:33:43
 * To change this template use File | Settings | File Templates.
 */
public class Point {
    private int x;
    private int y;

    public Point() {

    }
   
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    public String toString() {
        StringBuffer sb = new StringBuffer("Point(");
        sb.append(x).append(", ").append(y).append(")");
        return sb.toString();
    }
}

// PointConvertor 類
package com.jpleasure.conversion;

import org.apache.struts2.util.StrutsTypeConverter;

import java.util.Map;

/**
 * Created by IntelliJ IDEA.
 * User: ma.zhao@dl.cn
 * Date: 2007/09/04
 * Time: 12:34:18
 * To change this template use File | Settings | File Templates.
 */
public class PointConvertor extends StrutsTypeConverter {
   
    // 從字符串轉換為對象的方法。
    public Object convertFromString(Map map, String[] strings, Class aClass) {
        if (strings.length > 0) {
            String pointStr = strings[0];
            String[] pointStrArray = pointStr.split(",");
            if (pointStrArray.length == 2) {
                Point p = new Point();
                p.setX(Integer.parseInt(pointStrArray[0]));
                p.setY(Integer.parseInt(pointStrArray[1]));
                return p;
            } else {
                return null;
            }

        } else {
            return null;
        }
    }
   
    // 從對象轉換為字符串的方法。
    public String convertToString(Map map, Object o) {
        if (o instanceof Point) {
            return o.toString();
        } else {
            return "";
        }
    }
}

// 測試用PointAction類
package com.jpleasure.action;

import com.jpleasure.conversion.Point;
import com.opensymphony.xwork2.ActionSupport;

/**
 * Created by IntelliJ IDEA.
 * User: ma.zhao@dl.cn
 * Date: 2007/09/04
 * Time: 12:45:11
 * To change this template use File | Settings | File Templates.
 */
public class PointAction extends ActionSupport {
    private Point point;

    public Point getPoint() {
        return point;
    }

    public void setPoint(Point point) {
        this.point = point;
    }

    public String execute() {
        return SUCCESS;
    }
}

// JSP文件
<%--
  Created by IntelliJ IDEA.
  User: ma.zhao@dl.cn
  Date: 2007/09/04
  Time: 12:47:40
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head><title>Point Page</title></head>
<body>
<s:form action="point" namespace="/point" method="post">
    <s:textfield name="point"/>

    <s:property value="point"/>
    <s:submit/>
</s:form>
</body>
</html>


// PointAction配置文件類。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
        "http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

    <package name="point" namespace="/point" extends="struts-default">

        <action name="point" class="com.jpleasure.action.PointAction">
            <result>/point/Point.jsp</result>
            <!-- 定義input的result是為了處理轉換出錯的情況,在輸入格式不正確的情況下轉移到這個畫面 -->
            <result name="input">/point/Point.jsp</result>
        </action>
        <!-- Add actions here -->
    </package>
</struts>

復雜的類型轉換:
(1)處理Null值
有 些時候我們會被NullPointerException搞的焦頭爛額,為什么系統不能為我們定義了但是沒有初始化的對象建立一個空的Object的引用 呢?Struts2有這個功能,但是在默認情況下Struts2關閉了這個功能,要想開啟這個功能,需要在ParameterInterceptor開始 處理參數之前在ValueStack中將一個值開啟,這個值是:
InstantiatingNullHandler.CREATE_NULL_OBJECTS。
在Java代碼中InstantiatingNullHandler.CREATE_NULL_OBJECTS的值是:xwork.NullHandler.createNullObjects
創建空值對象的規則為:
  • 如果屬性聲明為Collection或List, 將返回一個ArrayList并賦值給空引用.
  • 如果屬性聲明為Map, 將返回一個HashMap并賦值給空引用.
  • 如果空值屬性是一個帶有無參構造函數的簡單Bean, 將使用ObjectFactory.buildBean(java.lang.Class, java.util.Map)方法創建一個實例.

(2)Collection和Map

簡單List轉換
//JSP代碼
<%--
  Created by IntelliJ IDEA.
  User: mazhao
  Date: 2007/09/04
  Time: 12:47:40
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head><title>Point Page</title></head>
<body>
<s:form action="point" namespace="/point" method="post">
    <s:textfield label="Point" name="point"/>

    <s:textfield label="References" name="references"/>
    <s:textfield label="References" name="references"/>
    <s:textfield label="References" name="references"/>
    <s:textfield label="References" name="references"/>
    <s:textfield label="References" name="references"/>
                   

    <s:property value="point"/>
    <s:submit/>
</s:form>
</body>
</html>

//Action代碼
package com.jpleasure.action;

import com.jpleasure.conversion.Point;
import com.opensymphony.xwork2.ActionSupport;

import java.util.List;

/**
 * Created by IntelliJ IDEA.
 * User: ma.zhao@dl.cn
 * Date: 2007/09/04
 * Time: 12:45:11
 * To change this template use File | Settings | File Templates.
 */
public class PointAction extends ActionSupport {
    private Point point;
    private List references;

    public Point getPoint() {
        return point;
    }

    public void setPoint(Point point) {
        this.point = point;
    }

    public List getReferences() {
        return references;
    }

    public void setReferences(List references) {
        this.references = references;
    }

    public String execute() {

        if(references == null) {
            System.out.println("references is null");
        } else {
            System.out.println("references length is:" + references.size());
           
            for(Object s: references) {
                System.out.println("" + s);
            }
        }

        return SUCCESS;
    }
}

對象類型List轉換(key-value pair 方式)
// Person 類型

package org.apache.struts2.showcase.conversion;

import java.io.Serializable;

/**
 *
 */
public class Person implements Serializable {
    private String name;
    private Integer age;

    public void setName(String name) { this.name = name; }
    public String getName() { return this.name; }

    public void setAge(Integer age) { this.age = age; }
    public Integer getAge() { return this.age; }
}

// PersionAction 類型

 package org.apache.struts2.showcase.conversion;

import java.util.List;

import com.opensymphony.xwork2.ActionSupport;

/**
 *
 */
public class PersonAction extends ActionSupport {

    private List persons;

    public List getPersons() { return persons; }
    public void setPersons(List persons) { this.persons = persons; }

 

    public String input() throws Exception {
        return SUCCESS;
    }

    public String submit() throws Exception {
        return SUCCESS;
    }
}
// PersonAction轉化配置文件PersonAction-conversion.properties

 

# PersonAction中persons屬性(List類型)中元素的類型

Element_persons=org.apache.struts2.showcase.conversion.Person

 

// JSP部分代碼

 <s:iterator value="new int[3]" status="stat">
        <s:textfield    label="%{'Person '+#stat.index+' Name'}"
                        name="%{'persons['+#stat.index+'].name'}" />
        <s:textfield    label="%{'Person '+#stat.index+' Age'}"
                        name="%{'persons['+#stat.index+'].age'}" />
  </s:iterator>
其中stat記錄了當前循環的信息,其中stat.index表示當前循環的下標。

所以上述代碼會生成如下的代碼:

<s:textfield    label="Person 1 Name"
                name="persons[0].name" />
<s:textfield    label="Person 1 Age"
                name="persons[0].age" />
<s:textfield    label="Person 2 Name"
                name="persons[1].name" />
<s:textfield    label="Person 2 Age"
                name="persons[1].age" />
<s:textfield    label="Person 3 Name"
                name="persons[2].name" />
<s:textfield    label="Person 3 Age"
                name="persons[2].age" />

 

對象類型List轉換(value 方式)

// Address 類型 

package org.apache.struts2.showcase.conversion;


/**
 * @version $Date: 2006-11-23 12:31:52 -0500 (Thu, 23 Nov 2006) $ $Id: Address.java 478625 2006-11-23 17:31:52Z wsmoak $
 */
public class Address {

    private String id;
    private String address;

    public String getId() { return id; }
    public void setId(String id) { this.id = id; }

    public String getAddress() { return address; }
    public void setAddress(String address) { this.address = address; }

}

// AddressAction 類型

 package org.apache.struts2.showcase.conversion;

import java.util.LinkedHashSet;
import java.util.Set;

import com.opensymphony.xwork2.ActionSupport;

/**
 * @version $Date: 2006-11-23 12:31:52 -0500 (Thu, 23 Nov 2006) $ $Id: AddressAction.java 478625 2006-11-23 17:31:52Z wsmoak $
 */
public class AddressAction extends ActionSupport {

    private Set addresses = new LinkedHashSet();

    public Set getAddresses() { return addresses; }
    public void setAddresses(Set addresses) { this.addresses = addresses; }


    public String input() throws Exception {
        return SUCCESS;
    }

    public String submit() throws Exception {
        System.out.println(addresses);
        return SUCCESS;
    }
}

 

//AddressAction轉換配置文件AddressAction-conversion.properties

KeyProperty_addresses=id
Element_addresses=org.apache.struts2.showcase.conversion.Address
CreateIfNull_addresses=true

 

// JSP代碼 

<s:form action="submitAddressesInfo" namespace="/conversion">
  <s:iterator value="%{new int[3]}" status="stat">
   <s:textfield label="%{'Address '+#stat.index}"
        name="%{'addresses(\\'id'+#stat.index+'\\').address'}" />
  </s:iterator>
  <s:submit />
 </s:form>

 

 上述代碼會轉換為:

<s:form action="submitAddressInfo" namespace="/conversion">
  <s:textfield label="Address 0"
      name="addresses('id0')" />
  <s:textfield label="Address 1"
      name="addresses('id1')" />
  <s:textfield label="Address 2"
      name="addresses('id2')" />
  <s:submit />
 </s:form>

 

注意兩個地方:

第一,沒有向服務器提交Address的id屬性,那么Address的id屬性是什么呢?
KeyProperty_addresses=id表示向服務器提交的內容的key部分("id0”, "id1”, "id2”)會被認定為Addredd的id。

第二,CreateIfNull_addresses=true表示及時客戶端沒有向服務器提交任何Address內容,服務器也會為AddressAction的addresses 建立一個長度為0的Set。

 

相關的一些經驗

開發的過程中不要一味的使用String類型,使用String類型無論在處理的速度還是內 存的使用上都不是最好的方式。一般情況下,Java的模型類(JavaBean, Action等都可以視為Java模型類,因為其中表示了模型的信息),一般情況下需要和數據庫中的類型一致。這樣才能保證最好的性能。

那么像java.util.Date,Integer等類型需要表示到JSP頁面上的時候還是需要表示為String類型的,但是Struts2都已經幫助實現了,所以請使用具體的類型吧,不要總是使用String類型。

 



ExtJS教程- Hibernate教程-Struts2 教程-Lucene教程