电脑技术学习

一个基于Annotation的持久层框架-去除getter和setter

dn001
内容: 使用J2SE 5.0的注解来去除getter和setter

摘要
getter/setter这种习惯用法一直是有问题的,它允许你的类更容易被访问,却使这些类失去了可维护性。J2SE 5.0的注解(或者说元数据)提供了另一种可能性。比起用自省寻找get/set方法,你可以用注解“标注类,然后在编译或者运行时访问那个注解。这篇文章不仅描述了注解机制,还介绍了一个基于XML的持久化机制的输出端,这个机制使用注解来标注类和字段。

我曾经在JavaWorld对getter/setter这种习惯用法的缺点做了很详细的讨论(请看资源)。这种习惯用法一开始是在JavaBean规范中被介绍的,以作为一种“标注对象属性的方法,这样,一个扩展的用户界面层工具(叫做BeanBox)可以为那个对象创建一系列的属性列表。你可以像下面那样提供方法来“标注属性。
String getFoo();
void setFoo( String newValue );


BeanBox使用Class类中的自省API获取方法列表,然后使用模式匹配来寻找getter/setter对。根据这些推断出属性是否存在,并确定属性的类型(在这个例子中,有一个String类型的Foo属性)。你是不会调用这些方法的,它们只会被BeanBox调用。

有趣的是,JavaBean规范的作者完全清楚getter/setter标注机制的问题所在(主要缺点已经在以前的文章中讨论过了,getter/setter方法暴露了过多的对象实现信息,所以底层类很难维护)。因此,设计者提供了大量的面向对象解决方法,比如BeanInfo和Customizer接口。用户实现了这些接口以后,就可以在没有setter/getter的条件下建立图形用户界面。不幸的是,这些过度复杂的面向对象方法在规范里很少提到。Getter/setter方法是简单的,可是如果你不能理解面向对象关系的维护问题,getter/setter方法好象是很合理的。因此,BeanInfo/Customizer方法就没落了,而getter/setter策略则像兔子一样快速繁殖。然而,你所经常看到的习惯用法并不是最好的做法。

JavaBean刚被提出时,许多人(包括我自己)赞成在Java中使用新的关键字来消除getter和setter。利用新引入的关键字的能力,我在早些时候像下面那样描述Foo属性:
private @property String foo;


因为foo是私有的,所以用新的关键字把它暴露给BeanBox并没有违反封装的原则。可是这时,引入新关键字有些离经叛道,尽管这个关键字不可能跟已经存在的标识符混淆,毕竟它们包含一个非法字符@。

当J2SE 5.0出现时,Sun已经领会到了它的微妙,并且对语言的主要语法做了一点调整。现在,你可以引入一个新的关键字(叫做注解)来表明一个属性会在编译时或者运行时被检查。你可以引入你选择的任何关键字。只需要做到这点,注解(关键字)必须有一个前导@符号,并且你必须像使用形容词一样使用注解(注解可以放在任何你可以声明static,final,或者public的地方)。最后,你可以抛开getter和setter了,一种更干净的语法能够做到相同的事情。

Java内置了两个很棒的关于注解的例子。想想这样一种情况,你的类继承自AWT/Swing的Adapter,可是却不小心拼错了基类方法的名字。你认为你覆写了基类的方法,实际上却没有。这种意料之外的继承是非常难以发现,但是如下的代码中的错误却很容易被编译器检测出来。
public class myListener implements MouseListener
{
@Overrides
void MousePressed(MouseEvent e)
{ System.out.println("Mouse button clicked!");
}
}


编译器在这里会抱怨,因为基类的方法叫做mousePressed()(m是小写的),而不是MousePressed(),就像类定义的那样。

另外,程序里的注解@Deprecated在语法上也比Javadoc中的要简洁(因为注释内容不会影响类的兼容性)。

有两种途径可以处理注解。首先,Class类的自省API可以获取关联到类的注解,以及关联到类的字段和方法的注解。BeanBox可以使用这种机制来寻找被标注的属性,并建立起属性列表。

如果你没有自己的BeanBox,那么还有另一种选择。JDK提供了apt (Annotation Processing Tool)处理器,它是javac的前端,能够理解注解,并允许你构建Java源代码。你需要给apt提供不同的注解处理器插件。在这个例子中,插件会建立一个包装类,像老式的BeanBox做的那样,使用getter/setter这种习惯用法来把被注解的属性暴露给外界。不过,(就算是按Sun的标准)apt的文档也是非常糟糕的。我会在以后的文章中在介绍如何使用。

在这篇文章中,我会展示一个小的持久化框架的“输出端来告诉你如何使用运行时注解。这个框架并没有解决全部持久关系问题的打算,但是它很容易的把一个对象的状态表示为一个XML字符串。你可以用这种原理来取代其他应用中的getter和setter,比如GUI或者帮助系统(通过注解一个类来说明详细的信息)。

版权声明:任何获得Matrix授权的网站,转载时请务必保留以下作者信息和链接
作者:Allen Holub ;deafwolf(作者的blog:http://blog.matrix.org.cn/page/deafwolf)
原文:http://www.javaworld.com/javaworld/jw-03-2005/jw-0321-toolbox.html
Matrix:http://www.matrix.org.cn/resource/article/44/44458_annotation+persistence.html
关键字:annotation;persistence

使用XMLExporter类

清单1示范了我的持久化框架是如何使用注解的,清单2则展示了相应的输出。

清单1. Test.java:使用XMLExporter
 1 package com.holub.persist.test;
2
3 import java.io.*;
4 import java.util.*;
5 import com.holub.persist.*;
6 import com.holub.persist.Exportable;
7 //----------------------------------------------------------------------
8 @Exportable
9 class Address
10 { private @Persistent String street;
11 private @Persistent String city;
12 private @Persistent String state;
13 private @Persistent("zipcode") int zip;
14
15 public Address( String street, String city, String state, int zip )
16 { this.street = street;
17 this.city = city;
18 this.state = state;
19 this.zip = zip;
20 }
21 }
22 //----------------------------------------------------------------------
23 public class Test
24 {
25 @Exportable( name="customer", description="A Customer" )
26 public static class Customer
27 {
28 @com.holub.persist.Persistent
29 private String name = "Allen Holub";
30
31 @Persistent
32 private Address streetAddress =
33 new Address( "1234 MyStreet",
34 "Berkeley", "CA", 99999 );
35 @Persistent
36 private StringBuffer notes = new StringBuffer( "Notes go here ");
37
38 private int garbage; // Is not persistant
39
40 @Persistent Collection invoices = new LinkedList();
41 { invoices.add( new Invoice(0) );
42 invoices.add( new Invoice(1) );
43 }
44 }
45
46 @Exportable
47 public static class Invoice
48 { private @Persistent int number;
49 public Invoice( int number ){ this.number = number; }
50 }
51
52 public static void main(String[] args ) throws IOException
53 { Customer x = new Customer();
54 XmlExporter out =
55 new XmlExporter(
56 new PrintWriter(System.out, true) );
57 out.flush( x );
58 }
59 }


Listing 2. Test output
 1