前言
我們不只一次抱怨信息系統中數據項變化不定,無法設計和實現Java Beans。我們也不只一次作過這樣的事情:數據項增加或減少了,我需要修改信息系統以求適應。我們對付這種變化莫定的需求還有一招:天天催企業領導或業務人員決定數據項,而不開始下面的設計和開發,還美名其為一個需求的"需求裡程碑"沒到,至少這個需求相關的設計和開發絕對不能開始。本文為這種情況提供了一種解決辦法,並美名其為"以動制動"。
JavaBean屬性
Java Beans 作為一種組件技術,其結構體系主要由屬性、方法和事件構成。象在其它面向對象技術中一樣,在Beans 中,屬性同樣起決定其當前狀態的作用。一個Bean的屬性的訪問和設置都必須通過訪問方法和設置方法來進行。
下面我們先舉一個的示例,然後對Beans 組件技術中的屬性支持進行解釋。
public class Author{
protected string name;
protected boolean married;
protected string[] books;
public string[] getBooks(){}
public void setBooks(integer[] x){}
public void setName(string n){}
public string getName(){}
public boolean isMarried(){}
public void setMarried(boolean bl){}
......
}
這是一個非常簡單的Bean,其中類的修飾符必須是public還有就是setXXX()/getXXX()方法必須遵循Beans內部的命名規則,因為Beans是根據這兩個方法來確定屬性的。其實,setXXX()/getXXX()方法是Beans的屬性機制的核心技術。
2.1 setXXX()/getXXX()方法
一個Bean屬性的定義完全取決於有無訪問者方法:設置器(setXXX())和獲取器(getXXX()),而與在類定義中有無顯示說明字段毫無關系,即上例中刪去那些protected修飾的字段與Bean毫無影響,因為Beans內部是根據有無訪問方法來確定屬性的存在與否的。為了使Beans能確認一個屬性,其設置器(setXXX())和獲取器(getXXX())必須遵循下列命名規則:
一個屬性名在訪問方法中必須以大寫字母開頭;
在其它地方以小寫字母開頭。
當然我們並不要求每個屬性都必須同時擁有這兩種訪問者,因為我們並不排除某個屬性只可讀或可寫。每種類型的屬性的設計必須遵循的規則叫這種屬性的設計模板,下面介紹各種類型屬性的設計模板。
2.1.1 簡單屬性
一個屬性為簡單屬性,當這個屬性不與外界有連帶關系時。簡單屬性中由於類型的復雜程度又有簡單類型屬性和數組屬性之分。
簡單類型屬性的設計模板
布爾型:
設置器: public boolean is<屬性名>(){}
獲取器: public void set<屬性名> (boolean bl ){}
其它類型的屬性的設計模板如下:
設置器: public void set<屬性名>( <屬性類型> x ){}
獲取器: public <屬性類型> get<屬性名>( ){}
數組屬性的設計模板
單個元素的設計模板
設置器: public void set<屬性名>( int i ,<屬性元素類型> x ){}
獲取器: public <屬性元素類型> get<屬性名>( int i ){}
整個數組的設計模板:
設置器: public void set<屬性名>( <屬性元素類型> [] x){}
獲取器: public <屬性元素類型>[] get<屬性名>( ){}
對於簡單屬性,不需要另外的附加類或接口。
2.1.2 相關屬性
相關屬性是這樣的一種屬性,它的改變能以事件的形式通知給對它感興趣的部分,即事件收聽者或目標。很明顯,這種屬性的作用在於它能使收聽者接到其改變事件後根據其中的信息產生一些行為,從而達到兩者之間的默契。相關屬性的訪問者方法遵循與簡單屬性相同的形式,就是說單從訪問者方法是看不出其與簡單屬性的區別,但它要另外的附加類或接口以及事件的傳播機制的支持(後面,我們會看到這同樣適用於約束屬性)。
實現一個關聯屬性涉及到三方,源Bean,目標Bean和協調代碼:
源Bean
源Bean必須提供屬性變化事件監聽器的注冊和解冊入口:
public void addpropertyChangeListener (propertyChangeListener pcListener){}
public void removepropertyChangeListener (propertyChangeListener pcListener){}
如只想通知目標Bean某個特定屬性的變化,可用下面特定屬性的注冊和解冊方法:
public void add<屬性名>Listener (propertyChangeListener pcListener){}
public void remove<屬性名>Listener (propertyChangeListener pcListener){}
這樣,目標Bean只會接到源Bean此屬性的變化的事件通知,減少了不必要的信息通信。另外,為了實現關聯屬性的方便,系統提供了一個幫助者類propertyChangeSupport,源Bean可實例化這個幫助者類,讓它來為我們管理和維護收聽者列表以及屬性變化事件的通知的觸發等工作。
目標Bean
目標Bean除了要實現propertyChangeListener接口外,還要用源Bean提供的注冊方法注冊自己。這樣,目標Bean的實現大體框架如下:
public class targetBean implements propertyChangeListener{
protected SourceBean source;
……
source=new SourceBean();
source.addpropertyChangeListener(this);
public void propertyChange(propertyChangeEvent e){
……
}
}
協調代碼
協調代碼的工作職責分為以下幾步:
負責創建源Bean和目標Bean;
利用源Bean的屬性變化事件監聽器的注冊入口注冊目標Bean;
改變源Bean的屬性的屬性
利用源Bean的屬性變化事件監聽器的解冊入口解冊目標Bean;
2.1.3 約束屬性
約束屬性是Beans所支持的最復雜最高級的屬性,它允許收聽者對屬性的改變提出否定意見。
與相關屬性類似,其設計與實現也要涉及到源Bean、目標Bean和協調代碼。只要把相關屬性設計中的property改成Vetoable(除了propertyChangeEvent外),不同的是為了能使目標Bean"反對"源Bean屬性的變化。Beans提供了一種異常propertyVetoException,只要目標Bean收到屬性改變的事件通知後,查看屬性的新值,如果不滿意,可拋出一個異常,讓源Bean放棄改變屬性到這個新值的念頭,這就是約束屬性中給目標Bean增加的"反對權利"。下面的簡單源Bean和目標Bean的偽代碼表述了約束屬性的實現視圖。
源Bean
public class SourceBean {
public void addVetoChangeListener (VetoChangeListener vpListener){}
public void removeVetoChangeListener (VetoChangeListener vpListener){}
/*由於屬性設置器本身不想處理異常,所以我們拋出異常,當然你也可以在屬性設置器處理異常,屬性變化監聽者對屬性的變化作出同意還是反對就是通過拋出異常的實現的。*/
public void setName(String n) throws propertyVetoException{
/*從下面目標的代碼可能拋出一個異常從而終止代碼的執行
*/
實例化一個propertyChangeEvent對象
執行屬性變化監聽者的vetoChange方法
/*如果上面的代碼拋出異常,下面這行代碼不會被執行,
也就是說監聽者阻止了屬性的變化
*/
name=n //修改屬性的值
}
}
目標Bean
public class TargetBean implements VetoChangeListener{
public void vetoChange(propertyChangeEvent e) throws propertyVetoException{
if e中的新值不滿意 then
生成 並拋出一個propertyVetoException實例
else
……
endif
}
}
協調代碼
協調代碼的工作職責分為以下幾步:
負責創建源Bean和目標Bean;
利用源Bean的屬性變化事件監聽器的注冊入口注冊目標Bean;
改變源Bean的屬性的屬性,並捕獲異常
利用源Bean的屬性變化事件監聽器的解冊入口解冊目標Bean;
2.2 標准Java Bean屬性總結
圖一 Java Bean屬性綜合圖解
如圖一所示,Java語言為Java Bean組件的屬性機制提供了良好的基礎,這些語言元素包括Java的面向對象技術,接口技術和異常技術等。Java Bean屬性命名規則和Java Bean屬性設計模板是Java Bean組件的屬性機制的規范。遵行這些規范,Java Bean組件的屬性可以分為三種:最基本的為簡單屬性,這種屬性只涉及屬性所在的Java Bean組件本身;相關屬性涉及到三方,包括源Bean、目標bean和協調代碼,源Bean為屬性的擁有者,目標bean為屬性變化事件的監聽者,協調代碼負責組織雙方,另外源Bean還可能實例化一個propertyChangeSupport對象來管理所有目標Bean,propertyChangeSupport對象的工作主要是保存所有目標Bean實例,並激發這些目標Bean的事件變化監聽方法;約束屬性在原理上和相關屬性一樣,只是增加了目標Bean對源Bean屬性變化的"反對權利"。
Java Bean組件技術是建立在Java基礎上的組件技術,它繼承了其的所有特點(如跨平台和面向對象),又引進了其它組件技術的思想,這兩個方面恰好是其能成為後起之秀的主要原因。它所能支持的屬性如相關屬性和約束屬性是其它組件技術所不能及的。
擴展javaBean屬性機制
無論是設計模式中值對象、視圖助手,MVC框架中的模型(Model),還是Enterprise Bean中的會話Bean,實體Bean,都和javaBean屬性息息相關。JavaBean組件屬性的優點我們前面已經總結過,隨著J2EE平台在企業應用中的廣泛使用,JavaBean組件屬性的缺陷也就顯露了出來:無法滿足企業應用動態變化的需要,原因在於javaBean屬性是編譯時的語言特性,它必須遵行一套命名規則和設計魔板。比如我按照某個企業的要求設計出了2000個實體Bean來滿足該企業對信息系統中業務數據模型的需要,過了一定時間後,他們的業務發生了一定的變化,要對數據模型擴充一部分數據項,可想而知我會有多辛苦。擴展javaBean屬性機制定義了五種屬性訪問策略,使得屬性的訪問代碼像腳本一樣在運行時決定,另外一個進步就是它支持List和Map屬性的元素屬性,也就是擴展javaBean屬性機制它不把一個Bean的某個List和Map成員看成一個整體屬性,而是動態地把這個List和Map成員的元素看成屬性,這樣無疑提供了一種無限擴展Bean屬性的能力,為解決由於數據項變化帶來的設計和實現的變更提供了技術基礎。
3.1 五種屬性訪問格式
Common-beanutils 1.6中的propertyUtils實用類使用Java語言的內省反射功能實現擴展屬性的設置器和獲取器。propertyUtils定義了引用一個特定Java bean屬性的五種格式:
簡單屬性,格式beanName.propName。propName標識了JavaBean beanName的一個屬性,這個屬性的獲取器和設置器的方法是通過JavaBean的標准內省功能決定的。如果要改變簡單屬性的值,必須要有設置器操作,可以想象成類似調用beanName.[getpropName()|setpropName(value)];
嵌套屬性,格式beanName.propName1.propName2.propName3。像簡單屬性一樣,第一個propName1元素被用來選擇一個屬性獲取器方法,在這個方法返回的對象上使用propName2的獲取器方法返回下一個對象,最後這個對象的屬性propName3被訪問或設置,可以想象成類似調用beanName.getpropName1().getpropName2().[getpropName3()|setpropName3(value)];
索引屬性,格式beanName.propName[index]。屬性propName1可以是一個數組, java.util.List或者JavaBean beanName有索引屬性獲取器和設置器操作。bean只需propName的獲取器方法,可以想象成類似調用beanName. [getpropName (index)|setpropName(index,value)];
映射屬性,格式beanName. propName(key)。propName是一個java.util.Map實現。bean只需propName的獲取器方法,可以想象成類似調用beanName. getpropName ().[get("key")|set("key",value);
組合屬性,格式beanName. propName1.propName2[index].propName3(key)。
3.2 代碼解釋
為了更有效和直觀的解釋擴展屬性的使用,在這裡列出了兩段代碼,一段代碼是Java Bean 代碼,一段為propertyUtils以五中格式訪問擴展屬性的代碼。
3.2.1 擴展屬性Java Bean
下面是支持這些擴展屬性的Java Bean代碼:
//TestBean.java
import java.util.*;
import java.io.*;
public class TestBean {
private String dupproperty[] =
{ "Dup 0", "Dup 1", "Dup 2", "Dup 3", "Dup 4" };
//propertyUtils只需要該索引屬性的一個獲取器操作就能
//使用get/setIndexedproperty方法訪問和設置索引和元素值
public String[] getDupproperty() {
System.out.println("getDupproperty");
return (this.dupproperty);
}
//下面的方法對propertyUtils的get/setIndexedproperty方法不關鍵,有則會調用這些方法
public String getDupproperty(int index) {
System.out.println("getDupproperty index");
return (this.dupproperty[index]);
}
public void setDupproperty(int index, String value) {
System.out.println("setDupproperty index value");
this.dupproperty[index] = value;
}
public void setDupproperty(String dupproperty[]) {
System.out.println("setDupproperty du[]");
this.dupproperty = dupproperty;
}
//這是一個索引屬性,除了支持"[]"型的數組屬性外,還支持申明為List類型的屬性
/**
* A List property accessed as an indexed property.
*/
private static List listIndexed = new ArrayList();
static {
listIndexed.add("String 0");
listIndexed.add("String 1");
listIndexed.add("String 2");
listIndexed.add("String 3");
listIndexed.add("String 4");
}
public List getListIndexed() {
return (listIndexed);
}
//嵌套屬性
private TestBean nested = null;
public TestBean getNested() {
System.out.println("getNested");
if (nested == null)
nested = new TestBean();
return (nested);
}
//這是一個映射屬性,必須申明為Map類型,propertyUtils只需要該屬性的一個獲取器操作就能
//使用get/setMappedproperty方法訪問和設置鍵和值
private Map hash = null;
public Map getHash(){
System.out.println("getHash");
if (hash == null) {
hash = new HashMap();
hash.put("First Key", "First Value");
hash.put("Second Key", "Second Value");
}
return (hash);
}
//下面的方法對在common-beanutils 1.6.1中propertyUtils的getMappedproperty方法不起作用,中不調用這些方法,
//而且不支持嵌套的映射屬性
//propertyUtils.setMappedproperty(bean, "nested.hash(Fifth Key)", "Fifth Value"); don't work!!
public Object getHash(String Key){
System.out.println("getHash Key");
return hash.get(Key);
}
public void setHash(String Key,Object value){
System.out.println("setHash Key value ");
hash.put(Key,value);
}
//這是一個簡單屬性,想在propertyUtils中修改必須有設置器操作
private String sample = null;
public String getSample() {
return sample;
}
public void setSample(String sample){
this.sample = sample;
}
}
3.2.2 使用propertyUtils實用類訪問擴展屬性
下面propertyUtils以五中格式訪問擴展屬性的代碼:
//testpropertyUtils.java
import org.apache..commons.beanutils.*;
import junit.framework.TestCase;
import junit.framework.Test;
import junit.framework.TestSuite;
public class testpropertyUtils extends TestCase {
public propertyUtilsTestCase(String name) {
super(name);
}
/**
* 實例化TestBean
*/
public void setUp() {
bean = new TestBean();
}
/**
* 測試索引屬性
*/
public void testSetIndexedValues() {
Object value = null;
//測試數組索引屬性
try {
propertyUtils.setproperty(bean,
"dupproperty(0)",
"New 0");
value =
propertyUtils.getproperty(bean,
"dupproperty(0) ");
assertNotNull("Returned new value 0", value);
assertTrue("Returned String new value 0",
value instanceof String);
assertEquals("Returned correct new value 0", "New 0",
(String) value);
} catch (Throwable t) {
fail("Threw " + t);
}
//測試List索引屬性
try {
propertyUtils.setproperty(bean,
"listIndexed(0) " ,
"New value");
value =
propertyUtils.getproperty(bean,
" listIndexed(0)");
assertNotNull("Returned new value 0", value);
assertTrue("Returned String new value 0",
value instanceof String);
assertEquals("Returned correct new value 0", " New value ",
(String) value);
} catch (Throwable t) {
fail("Threw " + t);
}
}
/**
* 測試映射屬性
*/
public void testSetMappedValues() {
Object value = null;
try {
propertyUtils.setproperty(bean,
"hash(key1)",
"New 0");
value =
propertyUtils.getproperty(bean,
"hash(key1)");
assertNotNull("Returned new value 0", value);
assertTrue("Returned String new value 0",
value instanceof String);
assertEquals("Returned correct new value 0", "New 0",
(String) value);
} catch (Throwable t) {
fail("Threw " + t);
}
}
/**
* 測試嵌套屬性
*/
public void testNestedValues() {
….
}
}
動態bean
相對標准Java Bean的編譯時靜態決定一個Bean的屬性,利用擴展javaBean屬性機制,能在運行時決定屬性的bean為動態bean。動態bean既有標准Java Bean的類型檢查機制又有擴展javaBean屬性機制的動態特點。下面我們從創建動態Bean和在配置文件中定義動態Bean的屬性兩方面介紹common-beanutils中動態bean機制。
4.1 運行時創建動態bean
動態bean具有動態屬性,也就是說可以由程序運行時構造bean的屬性,而不是像標准的javaBean在編譯時決定一個bean的屬性。
定義和訪問一個動態bean的步驟如下:
定義一個動態屬性Dynaproperty數組,動態屬性Dynaproperty定義了一個屬性的名字和對象類型;
用定義好的動態屬性數組實例化一個動態類;
由動態類返回一個動態bean;
可以用propertyUtils訪問和設置動態bean的屬性。
下面是定義和訪問動態bean的代碼
// TestDynaBean.java
import org.apache.commons.beanutils.*;
import java.util.*;
public class TestDynaBean {
public static void main(String[] args) {
TestBean bean = new TestBean();
Object value = null;
try{
Dynaproperty[] px = {
new Dynaproperty("subordinate", bean.getClass()),
new Dynaproperty("firstName", Class.forName("java.lang.String")),
new Dynaproperty("lastName", Class.forName("java.lang.String"))
};
DynaClass dynaClass = new BasicDynaClass("employee",null,
px );
DynaBean employee = dynaClass.newInstance();
propertyUtils.setproperty(employee,"subordinate", bean);
propertyUtils.setproperty(employee,"subordinate.listIndexed[0]","dy bean set");
propertyUtils.setproperty(employee,"firstName", "Fred");
propertyUtils.setproperty(employee,"lastName", "Flintstone");
System.out.println("subordinate.listIndexed[0]:");
System.out.println(propertyUtils.getproperty(employee,"subordinate.listIndexed[0]"));
System.out.println("firstName:" + propertyUtils.getproperty(employee, "firstName"));
System.out.println("lastName:" + propertyUtils.getproperty(employee, "lastName"));
}catch (Exception e ){
System.out.println(e.toString());
}
}
}
4.2 由文件配置動態bean的動態屬性
從配置文件配置動態bean的動態屬性好處在於既能固定又能更改動態Bean的屬性,這些屬性是這個動態Bean對外界的宣布的"訪問協議"。
從上面的代碼可以看出動態屬性Dynaproperty的主要構造函數接受兩個參數:第一個為屬性名稱,為字符串性,第二個為屬性的類型,為Class類型。從配置文件讀取的資料普通為字符串型,我們可以用ClassLoader把配置文件中字符串型的屬性類型轉化成Class類型的屬性類型。
下面struts 1.1 中struts-example.war的 formbeans.xml中的片斷體現了如何在配置文件中定一個動態bean的動態屬性:
<form-bean name="logonForm" type="org.apache.struts.validator.DynaValidatorForm">
<form-property name="username" type="java.lang.String"/>
<form-property name="password" type="java.lang.String"/>
</form-bean>
下面RequestUtils的代碼片斷體現了如何從字符串表示的屬性類型轉化成Class型的屬性類型:
public static Class applicationClass(String className)
throws ClassNotFoundException {
// Look up the class loader to be used
ClassLoader classLoader =
Thread.currentThread().getContextClassLoader();
if (classLoader == null) {
classLoader = RequestUtils.class.getClassLoader();
}
// Attempt to load the specified class
return (classLoader.loadClass(className));
}
這就是關於動態bean的所有奧秘,RequestUtils類代碼片斷public static ActionForm createActionForm就是這樣創建動態bean的。
結語
我們說Java Bean所能支持的屬性如相關屬性和約束屬性是其它組件技術所不能及的,這是非常先進的設計觀念和模式。但是標准Java Bean屬性是靜態的,是在編譯時決定的,不能滿足變化不定的企業數據項的需求。擴展Java Bean屬性機制提供了很好的思想,它不以Bean的List或Map成員看成一個整體屬性,而把其中的動態元素看成屬性,而且提供了腳本式的屬性訪問方法,從而為運行時動態管理(增,刪和改)屬性鋪平了道路。動態屬性繼續發揚了擴展Java Bean屬性機制的思路,考慮到增加程序的健壯性,保留了靜態屬性的類型檢查機制,另外動態屬性和屬性配置文件結合可以提供良好的"靜態和動態"平衡點,保證了動態Bean的動態屬性不會漫無邊際的擴張,這種動態性是有"章"可循的。
圖二 三種屬性機制的關系圖
這三種屬性機制的關系圖表明了它們之間的這種依賴和發展關系;擴展Java Bean屬性機制和動態屬性機制都可以很好地解決數據項變化的需求。