TA的每日心情 | 开心 2021-3-12 23:18 |
---|
签到天数: 2 天 [LV.1]初来乍到
|
摘要: 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;[/code] 因为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!");
}
}[/code] 编译器在这里会抱怨,因为基类的方法叫做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<Invoice> invoices = new LinkedList<Invoice>();
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 }[/code]
Listing 2. Test output
1 <!-- A Customer -->
2 <customer className="com.holub.persistent.test.Test$Customer" >
3 <name>
4 Allen Holub
5 </name>
6 <Address className="com.holub.persistent.test.Address" name="streetAddress" >
7 <street>
8 1234 MyStreet
9 </street>
10 <city>
11 Berkeley
12 </city>
13 <state>
14 CA
15 </state>
16 <zipcode>
17 99999
18 </zipcode>
19 </Address>
20 <notes classname="java.lang.StringBuffer">
21 Notes go here
22 </notes>
23 <invoices>
24 <Invoice className="com.holub.persistent.test.Test$Invoice" >
25 <number>
26 0
27 </number>
28 </Invoice>
29 <Invoice className="com.holub.persistent.test.Test$Invoice" >
30 <number>
31 1
32 </number>
33 </Invoice>
34 </invoices>
35 </customer>[/code] 我使用注释标识那些会以XML格式输出的类,以及这些类中会被存储的字段(示范而已,你不一定会存储全部的字段)。系统用简单的方法处理回路(一个Employee引用了一个Company,而Company则拥有它全部Employee的列表。):自引用的字段会被忽视。在这个例子中,我输出Company会输出Employee,但是这些Employee都不包含Company...Company元素。输出Employee则会输出Company(以及除了一开始那个的全部的Employee)。请注意,第二种情况很好的避免了把没有被标注的Employee的Company字段也给持久化了。如果一个需要被持久化的字段是容器或者Map,那么全部元素都会被保存。如果是Map,那么键和值都会被完好的保存下来。
抱歉,我发现这一段跟上面的代码不是一回事,为什么出现这种情况我也不清楚,请酌情处理。
让我们来看看系统是如何使用的:首先,请注意清单1第5行的引入声明。注释看起来像个中世纪的怪物:一部分是狮子,一部分是鹰,一部分是蛇。在现在这种情况下,它们的行为像是接口。而跟你刚看到的一样,注释的声明或多或少的像一个接口的声明。包含这种声明的文件可以正常编译,并且正常的引入你的代码之中。在清单1中,我同时使用了*格式和外部引入,只是为了说明这两种格式都会像预期的那样工作。
如果需要的话,你也可以使用完整的限定名来消除歧义。忽略@的话,
@com.holub.persist.Persistent private String name1;[/code] 和
import com.holub.persist.*;
//...
@Persistent private String name2;[/code] 都会像预期的那样工作。
回到清单1,拥有@Exportable前缀的类被标注为“可输出的”。 Address类(第9行)是一个例子,内部类Test.Customer也是如此(第26行)。
Test.Customer(第26行)的注释表明:注释是可以有参数的。像13行的@Persistent("zipcode")那样,注释可以使用单一的无名参数。当然,这样你就不能匹配两个参数了。
看一下测试程序所输出的清单2,你就可以知道这些参数都做了什么。一般来说,元素名是从类中的字段声明而来的。如果给出了这样的字段声明:
private @Persistent String name;[/code] 那么这个字段输出的值会是这样:
<name>
Allen Holub
</name>[/code] 这里有一个例外。如果一个类用@Exportable标注,然后这个类的对象会用类名代替字段名作为元素名来输出。例如,字段streetAddress(清单1第32行)是一个@Exportable类(Address类)的引用。所以,这个字段会输出为:
<Address>
...
</Address>[/code] 在注释中,你可以覆写这些缺省的元素名。清单1第25行的name="customer"参数说明框架会使用customer(用小写的c)来代替Customer(用大写的C——类名)。我也在这个注释中指定了第二个参数:@Exportable的参数description="A Customer"会被转化为输出文件第一行的注释。
显然,元素的内容来自字段,但是这里的情况有一点微妙。如果一个字段引用自一个没有@Exportable的对象,然后会返回toString()的值。清单1第36行的StringBuffer字段是这样的例子。那么字段会输出为:
</notes>
Notes go here
</notes>[/code] 文本Notes go here是notes.toString()返回的值。这种值不能被框架的“输入”端重新装载到对象里,除非该字段的类拥有一个String构造函数可以解析这个内容。
如果一个字段引用了一个@Exportable的对象,那么XML的子元素会被生成为应该表现的值,就如清单1第32行的streetAddress字段。
可输出的容器(和maps)中的所有元素都会被输出,但是全部元素都会被包装在一个外部元素里,所以这个容器看起来是一个整体。清单2底部的<invoices>元素就是这样的例子。在原来的对象中,相关的字段是这样声明的:
@Persistent Collection<Invoice> invoices = new LinkedList<Invoice>();
map的处理也是相似的,但是元素都会以name=属性来标识键(键对象的toString()方法会被调用)。例如:
<field_name_of_Map_field>
<ValueType name="value_returned_by_key_toString()_method" className="...">
value is displayed here
</ValueType">
</field_name_of_Map_field>[/code] 最后,请注意,这个例子是一个持久化框架,所以全部表示成对象的元素,输出时都会在className=属性中指明完全的类名称。如果知道字段名的话,也会在name=属性中输出。清单2第6行就是如此。
声明注释
在我们开始考虑实现之前,我必须说,注释的声明语法其实不是很好。许多奇怪的事情正在发生,而且很多语法例子都被证明不适合手头的任务,但是我们不得不忍受这些。
注释声明起来像是接口,但是有些奇怪得地方。清单3是一个简单的例子。接口名是注释名,并且你得在接口前加上@号。全部注释风格的接口都是从Annotation接口继承来的。
清单3. Persistent.java: 声明@Persistent
1 package com.holub.persist;
2
3 import java.lang.annotation.*;
4
5 @Retention(RetentionPolicy.RUNTIME)
6 @Target( ElementType.FIELD )
7 public @interface Persistent
8 { String value() default ""; // Won"t accept null;
9 }
10 [/code] 第5行和第六行的的“元注释”控制着注释会被如何使用。各种保留政策如下:
RetentionPolicy.CLASS 注释在类文件中,但是不能在运行时被访问。对于工作在类文件上的后置信息处理器很有用。
RetentionPolicy.RUNTIME 注释在类文件中,并且可以通过自省API来访问
RetentionPolicy.SOURCE 注释会在编译时刻被apt处理器所使用
元注释@Target表明哪种Java元素可以附在注释上。参数定义成java.lang.annotation.ElementType,多数类型是自说明的:
ElementType.ANNOTATION_TYPE 应用于其他注释的元注释
ElementType.CONSTRUCTOR 构造函数
ElementType.FIELD 字段
ElementType.LOCAL_VARIABLE 方法中的本地变量
ElementType.METHOD 方法
ElementType.PACKAGE 包
ElementType.PARAMETER 方法的参数
ElementType.TYPE 类,接口或者枚举声明
你也可以联合使用它们,就像初始化数组一样:
@Target( {ElementType.FIELD, ElementType.METHOD} );
还存在两种元注释,不过我还没在这里用过:
@Documented 注释会在缺省情况下被javadoc用于文档化
@Inherited 注释用于基类,则派生类通过自省可得到这些注释的信息,哪怕派生类自己没标注
回到清单3,方法名定义了注释参数。这里,名字value()被使用。如果一个方法使用了这个名字,那么你可以在代码中使用@annotation("value")格式(不使用arg= part)。方法的返回类型控制着参数的类型,这个类型必须是原始类型或者String。使用default子句来为参数指明一个初始值。如果没有default子句,那么参数就是必须的了。
现在,我们来看看注释@Exportable,这个注释有多个参数。
清单 4. Exportable.java: 声明@Exportable
1 package com.holub.persist;
2
3 import java.lang.annotation.ElementType;
4 import java.lang.annotation.Retention;
5 import java.lang.annotation.RetentionPolicy;
6 import java.lang.annotation.Target;
7
8 @Retention(RetentionPolicy.RUNTIME)
9 @Target( ElementType.TYPE )
10 public @interface Exportable
11 { String description() default "";
12 String name() default "";
13 String value() default "";
14 }
15 [/code] 请注意,我为每一个参数都像value()一样指明了默认值,所以这个注释使用起来有几种形式:
@Exportable
@Exportable("element_name")
@Exportable(name="element_name")
@Exportable(description="Comment goes here")
@Exportable(name="element_name" description="Comment goes here")[/code] 我实现了处理这个注释的代码,因此第2和第3两种格式处理起来是一样的,但是你从声明中是看不到实现细节的。
在运行时实现注释
继续实现,清单5中的XmlExporter类做了全部的工作:
清单 5. XmlExporter.java: XML输出类
1 package com.holub.persist;
2
3 import java.lang.reflect.*;
4 import java.io.*;
5 import java.util.*;
6
7 /** A very-simple persistent writer. Writes in XML format.
8 * Cyclic object graphs are handled by ignoring the
9 * cyclic field. For example, consider a Company that has references
10 * to its Employees, which have references to the Company.
11 * Exporting the Company will export all the Employees, but
12 * none of these Employees will contain a <Company>...</Company>
13 * element. Exporting an Employee will cause the Company (and
14 * all of it"s employees except the original one) to be exported.
15 * Note that this second situation is best avoided by not
16 * marking the Employee"s "Company" field as @Persistent.
17 */
18 public class XmlExporter
19 {
20 private PrintWriter out;
21 private int indentLevel = -1;
22 private boolean writeClassNames = false;
23 private Collection<Object> amVisiting = new ArrayList<Object>();
24
25 /** Create an XmlEporter.
26 *
27 * @param out send the XML output to this writer.
28 * @param writeClassNames if true, then synthesized element names
29 * will have a "className=" attribute.
30 */
31 public XmlExporter( PrintWriter out, boolean writeClassNames )
32 { this.out = out;
33 this.writeClassNames = writeClassNames;
34 }
35
36 /** Convenenience constructor, class names are written.
37 * @see #XmlExporter(PrintWriter,boolean)
38 */
39 public XmlExporter( PrintWriter out )
40 { this( out, true );
41 }
42
43 public void flush( Object obj ) throws IOException
44 { flush(obj, null, null );
45 }
46 /**
47 * Flush out a single object that"s represented as a class (not
48 * a primitive). The fields of the object are flushed
49 * recursively. The output takes the form:
50 * <PRE>
51 * <eleName className="..." name="..." >
52 * ...
53 * </eleName>
54 *
55 * The element name typically defaults to the class name. However,
56 * if the object is annotated and has a name= attribute (or
57 * an unnamed value), then that attribute specifies the
58 * element name. The className attribute is generated if
59 * this XmlExporter was created with constructor"s
60 * writeClassNames argument set to true.
61 * It holds the fully-qualified class name.
62 *
63 * @param obj The object to export. If the object is a Collection or
64 * Map, then the elements are exported as if they had
65 * been passed to flush(Object) one at a time.
66 *
67 * @param preferedElementName if not null, and the object is not annotated, then
68 * use this string instead of the class name for the
69 * element name.
70 * @param objName if not null, a name= attribute is
71 * included in the generated element, and this
72 * parameter determines the value.
73 * @throws IOException
74 */
75 public void flush( Object obj, String nameAttribute, String fallbackElementName ) throws IOException
76 { ++indentLevel;
77
78 if( amVisiting.contains(obj) ) // Cyclic object graph.
79 return; // Silently ignore. cycles.
80
81 amVisiting.add(obj);
82
83 if( obj instanceof Map )
84 { for( Iterator i = ((Map)obj).keySet().iterator(); i.hasNext(); )
85 { Object element = i.next();
86 flush( ((Map)obj).get(element), element.toString(), null );
87 }
88 }
89 else if( obj instanceof Collection )
90 { for( Iterator i = ((Collection)obj).iterator(); i.hasNext(); )
91 flush( i.next() );
92 }
93 else
94 { Exportable annotation =
95 obj.getClass().getAnnotation( Exportable.class );
96
97 if( fallbackElementName == null )
98 fallbackElementName = extractNameFromClass( obj );
99
100 String elementName = fallbackElementName;
101
102 if( annotation != null )
103 {
104 if( annotation.description().length() > 0 )
105 out.println("<!-- " + annotation.description() + " -->");
106
107 elementName = annotation.value();
108 if( elementName.length() == 0 ) // If it"s not specified in the value= attribute,
109 elementName = annotation.name(); // check the name= attribute.
110 if( elementName.length() == 0 ) // It"s not in the name= attribute either.
111 elementName = fallbackElementName;
112 }
113
114 out.println(
115 indent()
116 + "<"
117 + elementName
118 + ( !writeClassNames ? " " :
119 (" className="" + obj.getClass().getName()+"" ") )
120 + (nameAttribute == null ? " " :
121 (" name="" + nameAttribute +"" ") )
122 + ">" );
123
124 if( annotation != null ) // If the object is exportable,
125 flushFields( obj ); // then process its fields.
126 else
127 out.println( indent() + " " + obj.toString() );
128
129 out.println( indent() + "</" + elementName + ">");
130 }
131
132 amVisiting.remove(obj);
133 --indentLevel;
134 }
135
136 private void flushFields( Object obj ) throws IOException
137 { try
138 { Field[] fields = obj.getClass().getDeclaredFields();
139 for( Field f : fields )
140 {
141 Persistent annotation = f.getAnnotation( Persistent.class );
142 if( annotation == null )
143 continue;
144
145 f.setAccessible(true); // Make private fields accessible.
146
147 String value = null;
148 Class type = f.getType();
149 if(type == byte.class ) value=Byte.toString ( f.getByte(obj) );
150 if(type == short.class ) value=Short.toString ( f.getShort(obj) );
151 if(type == char.class ) value=Character.toString( f.getChar(obj) );
152 if(type == int.class ) value=Integer.toString ( f.getInt(obj) );
153 if(type == long.class ) value=Long.toString ( f.getLong(obj) );
154 if(type == float.class ) value=Float.toString ( f.getFloat(obj) );
155 if(type == double.class) value=Double.toString ( f.getDouble(obj) );
156 if(type == String.class) value= (String) ( f.get(obj) );
157
158 // If an element name is specified in the annotation, use it.
159 // Otherwise, use the field name as the element name.
160 String name = annotation.value();
161 if( name.length() == 0 )
162 name = f.getName();
163
164 if(value != null) // Then it"s a primitive type or a String.
165 { out.println (indent() + " <" + name + ">");
166 out.println( indent() + " " + value );
167 out.println( indent() + " </" + name + ">");
168 }
169 else if( f.get(obj) instanceof Collection
170 || f.get(obj) instanceof Map )
171 { out.println (indent() + " <" + name + ">");
172 flush( f.get(obj), f.getName(), null );
173 out.println( indent() + " </" + name + ">");
174 }
175 else
176 {
177 // The following if statement will kick out a type-safety warning
178 // from the compiler. Since we don"t actually know the type (so
179 // can"t cast it appropriately), there"s no way to eliminate
180 // this warning.
181
182 if( type.getAnnotation(Exportable.class) != null )
183 flush( f.get(obj), f.getName(), null );
184 else
185 flush( f.get(obj), null, name );
186 }
187 }
188 }
189 catch( IllegalAccessException e ) // Shouldn"t happen
190 { assert false : "Unexpected exception:
" + e ;
191 }
192 }
193
194 /** Get the class name from the prefix. If the fully-qualified name
195 * contains a $, assume it"s an inner class and the class name is
196 * everything to the right of the rightmost $. Otherwise, if the
197 * fully qualified name has a dot, then the class name is everything
198 * to the right of the rightmost dot. Otherwise, the name is the
199 * string returned from getClass().getName().
200 *
201 * @param obj
202 * @return
203 */
204 private String extractNameFromClass( Object obj )
205 { String name = obj.getClass().getName();
206 int index;
207 if( (index = name.lastIndexOf("$")) != -1 )
208 return name.substring( index + 1 );
209
210 if( (index = name.lastIndexOf(".")) != -1 )
211 return name.substring( index + 1 );
212
213 return name;
214 }
215 private static final String indents[] = new String[]
216 {
217 /* 00 */ "",
218 /* 01 */ " ",
219 /* 02 */ " ",
220 /* 03 */ " ",
221 /* 04 */ " ",
222 /* 05 */ " ",
223 /* 06 */ " ",
224 /* 07 */ " ",
225 /* 08 */ " ",
226 /* 09 */ " ",
227 /* 10 */ " ",
228 /* 11 */ " ",
229 /* 12 */ " ",
230 /* 13 */ " ",
231 /* 14 */ " ",
232 /* 15 */ " ",
233 };
234
235 private final String indent()
236 { return indents[ indentLevel %16 ];
237 }
238 }
239 [/code]
第一个有意思的方法是flush()(75行)。这个方法是顶级的方法,用来把整个对象转化为XML格式的输出流。flush()方法还是递归的,用来转化不是原始类型或者String的字段。方法的前几行设置了缩进的级别(所以XML看起来会漂亮些)。检测当前对象是否在amVisiting容器中,可以处理对象图中的回路。如果是,那么这个实例的flush()会被递归调用,并且这次处理中的一些调用就会被跳过。这种情况下,方法会返回。
下一段代码(从83行开始)处理了当前对象为Map或者容器的情况。集合中的元素会被轮流处理,就像他们依次转到flush()一样。Map与容器在处理时只有一点不同,持有键的值的name=...属性也会被flush()递归调用。
最后,我们开始解释处理注释的代码。你访问类对象来得到注释(94行):
请注意,我用getAnnotation()来从类中寻找特定的注释,并且返回类型被神奇的泛型自动转化为当前接口的类型。如果你不使用泛型语法,那么在Javadoc中,这个方法会显得十分混乱:
public <A extends Annotation> A getAnnotation(Class<A> annotationClass)[/code] 这个声明表明,类A(返回类型)必须继承自Annotation,并且参数也必须使用相同的泛型类型。实际上,你不需要转化返回值,但是,我讨厌这种语法。
你可以使用从getAnnotation()返回的对象,就像使用接口的引用一样。例如,代码中给出了这样的标注:
@Exportable(name="Fred")
//...[/code] 我可以从相关的name=参数中得到字符串值:
Exportable annotation = obj.getClass().getAnnotation( Exportable.class );
//...
annotation.name(); // Returns "Fred"[/code] 也请注意到这点,清单5从第107行开始,value()明确的为name=参数提供了一个缺省值。代码首先检测缺省值,如果值不存在,则使用名字代替。这种未命名参数的默认值必须在你的代码中手动处理。在注释的@interface声明中,是没有办法指定行为的。
方法flushFields()(清单5第136行)工作时跟flush()方法很相似,至少在处理注释上是这样。我用Class的getDeclaredFields()获取字段。我调用setAccessible(true)来告诉系统忽略访问权限,这样我就可以处理私有的字段了。然后flushFields()方法就可以输出原始类型或者String类型的参数了。它调用flush()来递归的输出除原始类型和String以外任意类的对象,包括容器或者Map字段。
结论
这篇文章介绍了一个关于注释的简单例子,但是它很有效的示范了如何使用运行时注释来消除基于getter/setter标注的习惯用法。对于所有有自省关系的类(比如类,方法,字段),你都可以用getAnnotation()方法来访问注释。像我在这篇文章一开始所说的,这只是拼图的一部分,因为注释通常用在编译器上,而不是运行时。我会在以后的文章中介绍如何做到这一点。
关于作者
Allen Holub从1979年以来加入计算机产业,从操作系统到编译器,到网络应用。他是一名顾问,提供面向对象设计和Java开发的指导及训练,致力于技术和设计,并在适当的时候写程序。他在NetReliance是一名CTO,并且是Ascenium和Ontometrics的顾问。Holub写了九本书(包括Holub on Patterns: Learning Design Patterns by Looking at Code, Taming Java Threads, and Compiler Design in C),并发表了100多篇文章。他对于JavaWorld和SD Times来说是非常有贡献的作者。
源码下载:http://file.javaxxz.com/2014/10/29/235757609.zip |
|