UML软件工程组织

数据库中数据项变化不定,如何设计Java Beans
龚永生 (摘自IBM中国)

  1. 前言

     

    我们不只一次抱怨信息系统中数据项变化不定,无法设计和实现Java Beans。我们也不只一次作过这样的事情:数据项增加或减少了,我需要修改信息系统以求适应。我们对付这种变化莫定的需求还有一招:天天催企业领导或业务人员决定数据项,而不开始下面的设计和开发,还美名其为一个需求的"需求里程碑"没到,至少这个需求相关的设计和开发绝对不能开始。本文为这种情况提供了一种解决办法,并美名其为"以动制动"。

  2. 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 简单属性

    一个属性为简单属性,当这个属性不与外界有连带关系时。简单属性中由于类型的复杂程度又有简单类型属性和数组属性之分。

     

    1. 简单类型属性的设计模板

      布尔型:

      设置器:public boolean is<属性名>(){}
      获取器:public void set<属性名> (boolean bl ){}

      其它类型的属性的设计模板如下:

      设置器:public void set<属性名>( <属性类型> x ){}
      获取器:public <属性类型> get<属性名>( ){}
    2. 数组属性的设计模板

      单个元素的设计模板

      设置器: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){
        ……
        }
      }
      

    • 协调代码

      协调代码的工作职责分为以下几步:

       

      1. 负责创建源Bean和目标Bean;
      2. 利用源Bean的属性变化事件监听器的注册入口注册目标Bean;
      3. 改变源Bean的属性的属性
      4. 利用源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
      }
      }
      

    • 协调代码

      协调代码的工作职责分为以下几步:

      1. 负责创建源Bean和目标Bean;
      2. 利用源Bean的属性变化事件监听器的注册入口注册目标Bean;
      3. 改变源Bean的属性的属性,并捕获异常
      4. 利用源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基础上的组件技术,它继承了其的所有特点(如跨平台和面向对象),又引进了其它组件技术的思想,这两个方面恰好是其能成为后起之秀的主要原因。它所能支持的属性如相关属性和约束属性是其它组件技术所不能及的。

  3. 扩展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属性的五种格式:

    1. 简单属性,格式beanName.propName。propName标识了JavaBean beanName的一个属性,这个属性的获取器和设置器的方法是通过JavaBean的标准内省功能决定的。如果要改变简单属性的值,必须要有设置器操作,可以想象成类似调用beanName.[getpropName()|setpropName(value)];
    2. 嵌套属性,格式beanName.propName1.propName2.propName3。像简单属性一样,第一个propName1元素被用来选择一个属性获取器方法,在这个方法返回的对象上使用propName2的获取器方法返回下一个对象,最后这个对象的属性propName3被访问或设置,可以想象成类似调用beanName.getpropName1().getpropName2().[getpropName3()|setpropName3(value)];
    3. 索引属性,格式beanName.propName[index]。属性propName1可以是一个数组, java.util.List或者JavaBean beanName有索引属性获取器和设置器操作。bean只需propName的获取器方法,可以想象成类似调用beanName. [getpropName (index)|setpropName(index,value)];
    4. 映射属性,格式beanName. propName(key)。propName是一个java.util.Map实现。bean只需propName的获取器方法,可以想象成类似调用beanName. getpropName ().[get("key")|set("key",value);
    5. 组合属性,格式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() {
    	….
    }
    
    }
    

  4. 动态bean

     

    相对标准Java Bean的编译时静态决定一个Bean的属性,利用扩展javaBean属性机制,能在运行时决定属性的bean为动态bean。动态bean既有标准Java Bean的类型检查机制又有扩展javaBean属性机制的动态特点。下面我们从创建动态Bean和在配置文件中定义动态Bean的属性两方面介绍common-beanutils中动态bean机制。

    4.1 运行时创建动态bean

    动态bean具有动态属性,也就是说可以由程序运行时构造bean的属性,而不是像标准的javaBean在编译时决定一个bean的属性。

    定义和访问一个动态bean的步骤如下:

    1. 定义一个动态属性Dynaproperty数组,动态属性Dynaproperty定义了一个属性的名字和对象类型;
    2. 用定义好的动态属性数组实例化一个动态类;
    3. 由动态类返回一个动态bean;
    4. 可以用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());
    	}
      }
    }
    
    

 

关于作者
龚永生,对java技术,Linux技术以及项目管理非常感兴趣。您可以通过gongys@legend.com与他联系。

 


版权所有:UML软件工程组织