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

package sample.annotation;

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


public @interface Broker
{

String name();

String address();

}

}

使用這個annotation
Import sample.annotation.broker;

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


public class Agent
{


public String getTelPhone ()
{

return "010-0592-2519280";

}

}


運行期得到這個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文件中的表示。以下為對比結果:
源碼:
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)


public @interface Broker
{

String name();

String address();

}

Javap結果:
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結果:
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;
// 以下為方法域,略
補充說明:我們都知道在java 1.0發布時,java class file的格式就已經定下來,要說明的是為了應未來的需要java class file設計了屬性說的機制。一直到J2SE1.4都沒有怎么改變。但這次為了更好的支持metadata技術,一共增加了8個屬性。分別是:
「EnclosingMethod」Attribute :Anonymous Class 或 Local Inner Class 使用此 Attribute 描述該Class 的Scope。
「Signature」 Attribute:Generics 的 Class、Method、或 Filed使用此 Attribute 來記錄所要的類型,因為java的范型采用了擦拭法。
「LocalVariableTypeTable」 Attribute:主要是給 debugger 使用,目的和「LocalVariableTable」 Attribute類似,只是「LocalVariableTable」 Attribute 記錄所要的參數表,而「LocalVariableTypeTable」 Attribute 記錄參數的類型。
「RuntimeVisibleAnnotations」 Attribute:確定該annotation可以被reflection的API返回,適用對象:Class、Method、Field
「RuntimeInvisibleAnnotations」 Attribute:確定該annotation無法被reflection的API返回,適用對象: Class、Method、Field。
「RuntimeVisibleParameterAnnotations」 Attribute:同「RuntimeVisibleAnnotations」 Attribute,適用對象:Method,(該Method 的參數
「RuntimeInvisibleParameterAnnotations」 Attribute:同「RuntimeInvisibleAnnotations」 Attribute,適用對象:Method,(該Method 的參數。
「AnnotationDefault」 Attribute:適用對象:Method,記錄默認值。
1.4. 為什么需要Annotation
在annotation之前我們已經廣泛使用另外一種元數據xml,為什么需要annotation。Annotation與xml的作為元數據的區別是什么——位置。Annotation寫在源代碼中,而xml寫在外部。
為什么要這樣?如果你開發過EJB,你一定為你的EJB寫過xml描述文件。當大量的EJB需要描述時,就出現了所謂的"descriptor hell"。這個也導致了著名的XDoclet的出現。而annotation出現可以避免這種descriptor hell。另外你更改了某個方法為其增加或者減少一個參數,你就對應的修改xml文件,而使用annotation則不必。使用annotation將開發和部署更方便,提供開發效率。
另外:使用xml的另一個問題是:很多Xml配置太過verbose。相比較EJB和Hibernate 或者Webwork可以明顯的發現不同。
1.5. 再議Annotation
在EJB3中,Annotation把開發和部署的工作合在一起。但是在一些企業環境中,開發人員并不控制諸如數據源名等(這些是部署部門和管理部門的工作),這樣把數據源名寫在xml中將比較好。
Annotation是本身靜態的,一旦添加或者修改annotation都需要重新編譯,在運行時讀取,這樣就喪失了運行時配置的能力。因此Annotations 不會取代xml,它只是提供了另一種途徑。并且我相信sun公司將在未來提供一個方式可以在運行期更改metadata。
關于這點TSS上有著很激烈的討論,很多開發人員提出:利用xml來更改annotation,并希望類似的方式能被采納為標準規范。比如使用如下格式:
<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>

當然也有不同意見: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....
關于為annotation提供動態配置能力的問題,其中一個網友認為: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...這讓我想起了類似的現象:JSP和TagLib。希望Annotation不會和TagLib有同樣的命運。
Annotation本身引入另一種類型的接口。在EJB3中確實使程序更加POJO,也消除了一些接口。并且編譯后的代碼也可以直接移植到另一個并不處理這些annotations的環境中(感謝VM在加載類時并不檢查那些annotations的classes,甚至這些類不在classpath中)。然而代碼也確實增加了另一些接口。這個表現在編譯期,如果沒有這些annotation classes,是編譯不過的。
另一個問題(還好不算很重要),關于annotation的namespace。在多層應用的系統中,可能會出現同樣的全局annotation的namespace沖突。比如一些公共的annotation,如@Transaction,將會被應用在很多個層次中。盡量讓namespace長些,可以避免這個問題。
1.6. 元數據編程的應用:
Annotation已經被集成到很多的java規范和標準中,很重要的是它已經被J2EE標準,如EJB3所采用,當然也被許多開源的組件體系如:ASPectJ。
Annotation最重要的應用將是AOP:由于annotation可以天然的表示系統中的另一個橫切面,同時Annotation的識別是通過反射得到的,所以Annotation很自然的應用到基于動態代理的AOP實現。AOP-Alliance也支持metadata handling。AspectJ也發布了基于annotation的新版本。
在實現AOP上,使用annotation也比使用XML有一個優勢:如前所述,annotation更像是形容詞和副詞,這樣比較不容易verbose。當然這個是相對的,在實際的實現中更依賴開發人員的努力。
這里,筆者將展示一個不完整也不成熟的基于annotation的AOP例子代碼——關于銀行卡的例子。
功能需求:銀行卡業務分為轉帳,查詢余額,查詢明細,POS消費等。這其中轉帳和POS消費是要收費的(轉帳收取的是用戶的手續費,而POS消費收取的是商家的手續費),另外POS消費還可能有積分的(比如筆者的牡丹貸記卡)。消費轉帳都要記錄明細。但查詢余額就不需要記錄在明細中。
代碼如下(在這個例子沒有用動態代理也沒有用已有的AOP框架,使代碼看起來簡單些)
1
// 銀行卡對象
2
3
public class Card
{
4
5
private String account;
6
7
//some field method
8
9
}
10
11
Annotation:
12
13
// 手續費
14
15
// type= "user", 表示收取用戶手續費; type= "Biz", 表示收取商家手續費
16
17
public @interface Fee
{
18
19
String type();
20
21
}
22
23
// 積分
24
25
public @interface Index
{
26
27
}
28
29
// 記錄明細
30
31
public @interface BizLog
{
32
33
}
34
35
// 事務處理
36
37
public @interface Transaction
{
38
39
}
40
41
// 業務接口
42
43
public interface BizAction
{
44
45
void execute(Card card, RunData rundata);
46
47
}
48
49
// 轉帳業務
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消費
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
// 查詢明細
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
// 業務操作監視器接口
100
101
public interface BizActionMonitor
{
102
103
void execute(BizAction action, RunData rundata);
104
105
}
106
107
// 業務操作監視器實現
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)
{ // 計算手續費 }
118
119
if (annotation instanceof Index)
{ //計算積分 }
120
121
if (annotation instanceof Transaction)
{ // 準備事務 }
122
123
if (annotation instanceof BizLog)
{ // 記錄明細 }
124
125
}
126
127
}
128
129
}
130
131
// 控制器對象
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
// 運行時數據
148
149
public interface RunData
{
150
151
// some method
152
153
}
154
155
// 用戶設備(POS機, ATM或者柜臺終端)接口
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()
{ // 某個子類實現 }
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. 結束語:
本文討論了annotation技術,展示了annotation的class文件格式,并討論了annotation技術本身的優勢和不足,并于現有的xml技術加以比較,展望了annotation技術的應用前景AOP。
限于筆者自身的水平(包括技術水平和寫作水平),技術上對annotation的學習比較有限,寫作上也感覺好多話無法用文字來表達,因而本文的代碼會比較多(大概有一半以上)。