MetaData Programme
1.1. 什么是元數(shù)據(jù)編程
什么是元數(shù)據(jù),元數(shù)據(jù)就是描述數(shù)據(jù)的數(shù)據(jù)(data about data)。最明顯的例子是XML Schema,xml schema就是描述xml的數(shù)據(jù),所以它是元數(shù)據(jù)。另一個(gè)例子是數(shù)據(jù)庫,比如我們可以查詢數(shù)據(jù)庫中有幾個(gè)表,每個(gè)表都有什么字段,這些數(shù)據(jù)就是元數(shù)據(jù)。Office:office" />
在開發(fā)的世界里,元數(shù)據(jù)就是能夠綁定到一個(gè)類的附加信息,在靜態(tài)或者運(yùn)行時(shí)間。JCR175給我們提供annotation就是一種元數(shù)據(jù)。
不過在這之前一個(gè)我們已經(jīng)廣泛使用的元數(shù)據(jù)是XML,如就是EJB的XML發(fā)布描述符中,你需要定義基于每一個(gè)方法的事務(wù)屬性。應(yīng)用服務(wù)器指導(dǎo)什么時(shí)候,什么地方開始,掛起或者提交一個(gè)事務(wù),因?yàn)槟阍?/span>BEAN的XML的配置文件中的元數(shù)據(jù)內(nèi)已經(jīng)定義如方法:Required,RequiresNew,Support等等,它們綁定在你的EJB類和事務(wù)管理之間。XDoclet是另一個(gè)元數(shù)據(jù)的例子。
1.2. Annotation的意義和簡單例子
JDK1.5提供的annotation與我們所常見的classes、fieldss和methods間是什么關(guān)系。如下:如果說類和數(shù)據(jù)成員是名詞,方法是動(dòng)詞,那么annotation就是形容詞或者副詞,分別描述它們的所具有屬性。
好,現(xiàn)在就來實(shí)現(xiàn)一個(gè)annotation
import Java.lang.annotation.Retention;

package sample.annotation;

@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)


public @interface Broker
{

String name();

String address();

}

}

使用這個(gè)annotation
Import sample.annotation.broker;

@Broker(name="anders", address="xiamen")


public class Agent
{


public String getTelPhone ()
{

return "010-0592-2519280";

}

}


運(yùn)行期得到這個(gè)annotation

public class Main
{


public static void main(String[] args)
{

Agent agent = new Agent();


try
{

Annotation[] a = agent.getClass().getMethod("getBrokerName").getAnnotations();


for (int i=0; i<a.length ; i++)
{


if( a[i] instanceof Broker)
{

Broker broker = (Broker)a[i];

System.out.println(broker.name());

}

}

}


catch(Exception e)
{

e.printStackTrace(System.out);

}

}

}


1.3. Annotation的class文件格式
利用sun公司的提供的javap,我們可以看到annotation的在class文件中的表示。以下為對(duì)比結(jié)果:
源碼:
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)


public @interface Broker
{

String name();

String address();

}

Javap結(jié)果:
Compiled from "Broker.java"
interface Broker extends java.lang.annotation.Annotation
SourceFile: "Broker.java"
minor version: 0
major version: 0
Constant pool:
const #1 = class #9; // Broker
const #2 = class #10; // Object
const #3 = class #11; //Annotation
const #4 = Asciz name;
const #5 = Asciz ()Ljava/lang/String;;
const #6 = Asciz address;
const #7 = Asciz SourceFile;
const #8 = Asciz Broker.java;
const #9 = Asciz Broker;
const #10 = Asciz java/lang/Object;
const #11 = Asciz java/lang/annotation/Annotation;
{
public abstract java.lang.String name();
public abstract java.lang.String address();
}
源碼:
@Broker(name="anders", address="xiamen")


public class Agent
{


public String getTelPhone()
{

return "0592-2519580";

}

}


Javap結(jié)果:
Compiled from "Agent.java"
public class Agent extends java.lang.Object
SourceFile: "Agent.java"
RuntimeVisibleAnnotations: length = 0x10
00 01 00 11 00 02 00 12 73 00 13 00 14 73 00 15
minor version: 0
major version: 0
Constant pool:
const #1 = Method #4.#22;// java/lang/Object."<init>":()V
const #2 = String #23; // 0592-2519580
const #3 = class #24; // Agent
const #4 = class #25; // Object
const #5 = Asciz <init>;
const #6 = Asciz ()V;
const #7 = Asciz Code;
const #8 = Asciz LineNumberTable;
const #9 = Asciz LocalVariableTable;
const #10 = Asciz this;
const #11 = Asciz LAgent;;
const #12 = Asciz getTelPhone;
const #13 = Asciz ()Ljava/lang/String;;
const #14 = Asciz SourceFile;
const #15 = Asciz Agent.java;
const #16 = Asciz RuntimeVisibleAnnotations;
const #17 = Asciz LBroker;;
const #18 = Asciz name;
const #19 = Asciz anders;
const #20 = Asciz address;
const #21 = Asciz xiamen;
const #22 = NameAndType#5:#6;// "<init>":()V
const #23 = Asciz 0592-2519580;
const #24 = Asciz Agent;
const #25 = Asciz java/lang/Object;
// 以下為方法域,略
補(bǔ)充說明:我們都知道在java 1.0發(fā)布時(shí),java class file的格式就已經(jīng)定下來,要說明的是為了應(yīng)未來的需要java class file設(shè)計(jì)了屬性說的機(jī)制。一直到J2SE1.4都沒有怎么改變。但這次為了更好的支持metadata技術(shù),一共增加了8個(gè)屬性。分別是:
「EnclosingMethod」Attribute :Anonymous Class 或 Local Inner Class 使用此 Attribute 描述該Class 的Scope。
「Signature」 Attribute:Generics 的 Class、Method、或 Filed使用此 Attribute 來記錄所要的類型,因?yàn)?/span>java的范型采用了擦拭法。
「LocalVariableTypeTable」 Attribute:主要是給 debugger 使用,目的和「LocalVariableTable」 Attribute類似,只是「LocalVariableTable」 Attribute 記錄所要的參數(shù)表,而「LocalVariableTypeTable」 Attribute 記錄參數(shù)的類型。
「RuntimeVisibleAnnotations」 Attribute:確定該annotation可以被reflection的API返回,適用對(duì)象:Class、Method、Field
「RuntimeInvisibleAnnotations」 Attribute:確定該annotation無法被reflection的API返回,適用對(duì)象: Class、Method、Field。
「RuntimeVisibleParameterAnnotations」 Attribute:同「RuntimeVisibleAnnotations」 Attribute,適用對(duì)象:Method,(該Method 的參數(shù)
「RuntimeInvisibleParameterAnnotations」 Attribute:同「RuntimeInvisibleAnnotations」 Attribute,適用對(duì)象:Method,(該Method 的參數(shù)。
「AnnotationDefault」 Attribute:適用對(duì)象:Method,記錄默認(rèn)值。
1.4. 為什么需要Annotation
在annotation之前我們已經(jīng)廣泛使用另外一種元數(shù)據(jù)xml,為什么需要annotation。Annotation與xml的作為元數(shù)據(jù)的區(qū)別是什么——位置。Annotation寫在源代碼中,而xml寫在外部。
為什么要這樣?如果你開發(fā)過EJB,你一定為你的EJB寫過xml描述文件。當(dāng)大量的EJB需要描述時(shí),就出現(xiàn)了所謂的"descriptor hell"。這個(gè)也導(dǎo)致了著名的XDoclet的出現(xiàn)。而annotation出現(xiàn)可以避免這種descriptor hell。另外你更改了某個(gè)方法為其增加或者減少一個(gè)參數(shù),你就對(duì)應(yīng)的修改xml文件,而使用annotation則不必。使用annotation將開發(fā)和部署更方便,提供開發(fā)效率。
另外:使用xml的另一個(gè)問題是:很多Xml配置太過verbose。相比較EJB和Hibernate 或者Webwork可以明顯的發(fā)現(xiàn)不同。
1.5. 再議Annotation
在EJB3中,Annotation把開發(fā)和部署的工作合在一起。但是在一些企業(yè)環(huán)境中,開發(fā)人員并不控制諸如數(shù)據(jù)源名等(這些是部署部門和管理部門的工作),這樣把數(shù)據(jù)源名寫在xml中將比較好。
Annotation是本身靜態(tài)的,一旦添加或者修改annotation都需要重新編譯,在運(yùn)行時(shí)讀取,這樣就喪失了運(yùn)行時(shí)配置的能力。因此Annotations 不會(huì)取代xml,它只是提供了另一種途徑。并且我相信sun公司將在未來提供一個(gè)方式可以在運(yùn)行期更改metadata。
關(guān)于這點(diǎn)TSS上有著很激烈的討論,很多開發(fā)人員提出:利用xml來更改annotation,并希望類似的方式能被采納為標(biāo)準(zhǔn)規(guī)范。比如使用如下格式:
<class name="org.hibernate.Item">

@Entity

@Table(name="AUCTION_ITEM")

<method sig="getBar()">@Transient</method>

<method sig="doSomething(int, String)">

@Tx(REQUIRES_NEW)

</method>

</class>

當(dāng)然也有不同意見:But then, I think of "overriding" as a different problem to "xml deployment descriptors", and so I think we need two solutions. I think Cedric and Bill are trying to kill two birds with one stone, so maybe their proposals are better....
關(guān)于為annotation提供動(dòng)態(tài)配置能力的問題,其中一個(gè)網(wǎng)友認(rèn)為:Sun make it real pain to do the deployment XML so that they can introduce annotation to fix it. The annotation can make code/deployment coupling so strong that Sun can come out with a new way (annotation interceptor in jdk 1.6? :)) for fixing it. and the cycles goes on...這讓我想起了類似的現(xiàn)象:JSP和TagLib。希望Annotation不會(huì)和TagLib有同樣的命運(yùn)。
Annotation本身引入另一種類型的接口。在EJB3中確實(shí)使程序更加POJO,也消除了一些接口。并且編譯后的代碼也可以直接移植到另一個(gè)并不處理這些annotations的環(huán)境中(感謝VM在加載類時(shí)并不檢查那些annotations的classes,甚至這些類不在classpath中)。然而代碼也確實(shí)增加了另一些接口。這個(gè)表現(xiàn)在編譯期,如果沒有這些annotation classes,是編譯不過的。
另一個(gè)問題(還好不算很重要),關(guān)于annotation的namespace。在多層應(yīng)用的系統(tǒng)中,可能會(huì)出現(xiàn)同樣的全局annotation的namespace沖突。比如一些公共的annotation,如@Transaction,將會(huì)被應(yīng)用在很多個(gè)層次中。盡量讓namespace長些,可以避免這個(gè)問題。
1.6. 元數(shù)據(jù)編程的應(yīng)用:
Annotation已經(jīng)被集成到很多的java規(guī)范和標(biāo)準(zhǔn)中,很重要的是它已經(jīng)被J2EE標(biāo)準(zhǔn),如EJB3所采用,當(dāng)然也被許多開源的組件體系如:ASPectJ。
Annotation最重要的應(yīng)用將是AOP:由于annotation可以天然的表示系統(tǒng)中的另一個(gè)橫切面,同時(shí)Annotation的識(shí)別是通過反射得到的,所以Annotation很自然的應(yīng)用到基于動(dòng)態(tài)代理的AOP實(shí)現(xiàn)。AOP-Alliance也支持metadata handling。AspectJ也發(fā)布了基于annotation的新版本。
在實(shí)現(xiàn)AOP上,使用annotation也比使用XML有一個(gè)優(yōu)勢:如前所述,annotation更像是形容詞和副詞,這樣比較不容易verbose。當(dāng)然這個(gè)是相對(duì)的,在實(shí)際的實(shí)現(xiàn)中更依賴開發(fā)人員的努力。
這里,筆者將展示一個(gè)不完整也不成熟的基于annotation的AOP例子代碼——關(guān)于銀行卡的例子。
功能需求:銀行卡業(yè)務(wù)分為轉(zhuǎn)帳,查詢余額,查詢明細(xì),POS消費(fèi)等。這其中轉(zhuǎn)帳和POS消費(fèi)是要收費(fèi)的(轉(zhuǎn)帳收取的是用戶的手續(xù)費(fèi),而POS消費(fèi)收取的是商家的手續(xù)費(fèi)),另外POS消費(fèi)還可能有積分的(比如筆者的牡丹貸記卡)。消費(fèi)轉(zhuǎn)帳都要記錄明細(xì)。但查詢余額就不需要記錄在明細(xì)中。
代碼如下(在這個(gè)例子沒有用動(dòng)態(tài)代理也沒有用已有的AOP框架,使代碼看起來簡單些)
1
// 銀行卡對(duì)象
2
3
public class Card
{
4
5
private String account;
6
7
//some field method
8
9
}
10
11
Annotation:
12
13
// 手續(xù)費(fèi)
14
15
// type= "user", 表示收取用戶手續(xù)費(fèi); type= "Biz", 表示收取商家手續(xù)費(fèi)
16
17
public @interface Fee
{
18
19
String type();
20
21
}
22
23
// 積分
24
25
public @interface Index
{
26
27
}
28
29
// 記錄明細(xì)
30
31
public @interface BizLog
{
32
33
}
34
35
// 事務(wù)處理
36
37
public @interface Transaction
{
38
39
}
40
41
// 業(yè)務(wù)接口
42
43
public interface BizAction
{
44
45
void execute(Card card, RunData rundata);
46
47
}
48
49
// 轉(zhuǎn)帳業(yè)務(wù)
50
51
@Fee(type="user")
52
53
@Transaction
54
55
@BizLog
56
57
public class TransferAction implements BizAction
{
58
59
public void execute(Card card, RunData rundata)
{
60
61
//To change body of implemented methods use File | Settings | File Templates.
62
63
}
64
65
}
66
67
// POS消費(fèi)
68
69
@Fee(type="Biz")
70
71
@Transaction
72
73
@BizLog
74
75
@Index
76
77
public class POSAction implements BizAction
{
78
79
public void execute(Card card, RunData rundata)
{
80
81
//To change body of implemented methods use File | Settings | File Templates.
82
83
}
84
85
}
86
87
// 查詢明細(xì)
88
89
public class QueryDetail implements BizAction
{
90
91
public void execute(Card card, RunData rundata)
{
92
93
//To change body of implemented methods use File | Settings | File Templates.
94
95
}
96
97
}
98
99
// 業(yè)務(wù)操作監(jiān)視器接口
100
101
public interface BizActionMonitor
{
102
103
void execute(BizAction action, RunData rundata);
104
105
}
106
107
// 業(yè)務(wù)操作監(jiān)視器實(shí)現(xiàn)
108
109
public class BizActionMonitorImpl implements BizActionMonitor
{
110
111
public void execute(BizAction action, RunData rundata)
{
112
113
Annotation[] annotations = action.getClass().getAnnotations();
114
115
for(Annotation annotation : annotations)
{
116
117
if (annotation instanceof Fee)
{ // 計(jì)算手續(xù)費(fèi) }
118
119
if (annotation instanceof Index)
{ //計(jì)算積分 }
120
121
if (annotation instanceof Transaction)
{ // 準(zhǔn)備事務(wù) }
122
123
if (annotation instanceof BizLog)
{ // 記錄明細(xì) }
124
125
}
126
127
}
128
129
}
130
131
// 控制器對(duì)象
132
133
public class controller
{
134
135
private BizActionMonitor monitor;
136
137
public void execute(BizActionUI, rundata)
{
138
139
BizAction action = getAction(BizActionUI);
140
141
monitor.execute(action, rundata);
142
143
}
144
145
}
146
147
// 運(yùn)行時(shí)數(shù)據(jù)
148
149
public interface RunData
{
150
151
// some method
152
153
}
154
155
// 用戶設(shè)備(POS機(jī), ATM或者柜臺(tái)終端)接口
156
157
public class BizActionUI
{
158
159
private RunData rundata;
160
161
private Controller controller;
162
163
public BizActionUI(RunData rundata, Controller controller)
{
164
165
this.rundata = rundata;
166
167
this.controller = controller;
168
169
}
170
171
public void execute()
{ // 某個(gè)子類實(shí)現(xiàn) }
172
173
public void commit()
{
174
175
controller.execute(this, rundata);
176
177
}
178
179
}
180
181
public class Main
{
182
183
private Rundata rundata;
184
185
private Controller controller;
186
187
public populateUI(command)
{
188
189
BizActionUI ui = getUI(command);
190
191
ui.execute();
192
193
}
194
195
public BizActionUI getUI(command)
{
196
197
//
198
199
BizActionUI ui
200
201
if( //
.){
202
203
ui = new SomeBizActionUI(rundata, controller);
204
205
}
206
207
return ui;
208
209
}
210
211
public static main(String[] args)
{
212
213
//
214
215
Main main = new Main();
216
217
main.populateUI(command)
218
219
//
220
221
}
222
223
}
224
225
1.7. 結(jié)束語:
本文討論了annotation技術(shù),展示了annotation的class文件格式,并討論了annotation技術(shù)本身的優(yōu)勢和不足,并于現(xiàn)有的xml技術(shù)加以比較,展望了annotation技術(shù)的應(yīng)用前景AOP。
限于筆者自身的水平(包括技術(shù)水平和寫作水平),技術(shù)上對(duì)annotation的學(xué)習(xí)比較有限,寫作上也感覺好多話無法用文字來表達(dá),因而本文的代碼會(huì)比較多(大概有一半以上)。