作成 2005/1/12
EclipseもそろそろJ2SE5対応になってきたので、J2SE5の新機能に、いろいろ触りはじめてみる。まずはメタデータから。
J2SE5から、Javaソースコードにアノテーションを使って、メタデータを埋め込むことができるようになりました。
メタデータは、データに関するデータです。Javaでいうと、Javaソースコード自体についてのデータになります。英語で、メタ○○というと○○をあらわす○○という風の意味らしいです(メタモデルはモデルのモデル、メタメタモデルはモデルのモデルのモデル)。
Javaソースコードにメタデータを埋め込むには、アノテーション(注釈)を記述します。例えば、似たような仕組みに、JavaDocのタグがあります。@param、@returnといった記述がアノテーション。アノテーションによって埋め込まれたデータがメタデータになります。
ソースコードにメタデータを埋め込む仕組みは、いろいろと便利で、XDocletなどのツールでよく利用されていました。しかし、JavaDocにドキュメントではないものをいろいろ埋め込むのもナニですし、ツールによって、書式が違うのは後で困るかもということで、JSR-175でJavaの標準仕様になりました。
ソースコードのメタデータは、もともと.NETなどでよく利用されていた仕組みらしく、EoDに役立ちます。なお、現在、JDKで提供されているメタデータの仕組みは、「単にアノテーションを埋め込む」仕組みだけなので、アプリケーション開発者が使えるのようになるには、埋め込まれたアノテーションをどうこうする、というツールの登場が必要になります(一応、コンパイラやJavaDocジェネレータでちょっとした機能は提供されています。またリフレクションでメタデータを読み取ることはできます)。
ここでは、簡単なアノテーションを作成し、リフレクションで読み込んでみます。
アノテーションクラス(インターフェイス)はこれです。interfaceの前に@がついているのがアノテーションだよ、ということを表します。なお、@Retentionは必須ではありませんが、リフレクションで読み込む場合は指定する必要があります(指定しないデフォルトでは、バイトコードにアノテーションは組み込まれない)。
package test1;
import java.lang.annotation.*;;
@Retention(RetentionPolicy.RUNTIME)
public @interface Hoge {
public String value1();
public String value2();
}
次のクラスは普通のクラスですが、@でクラスにアノテーションを付加しています。Hogeインターフェイスで指定したメンバが、アノテーションの引数で指定できます。アノテーションをサポートしたIDEであれば、指定できるできないのチェックぐらいはしてくれるでしょう(Eclipse3.1は一応できるようです)。
package test1;
@Hoge(value1="m1",value2="m2")
public class Main {
public static void main(String[] args) {
Hoge hoge = Main.class.getAnnotation(Hoge.class);
System.out.println(
"value1=" + hoge.value1()
+ ",value2=" + hoge.value2()
);
}
}
実行すると、こうなります。
value1=m1,value2=m2
もう一歩進んで、アノテーション記述で、アスペクトみたいなものをかけてみましょう。
リフレクションで実行時にアノテーションをひろって、あれこれします。これをメソッド実行をアノテーションにしただけです。
アノテーションを読み込むところはこんな感じになります。Method#getAnnotatios()してるだけですが。。。なお、ダイナミックプロキシでinvokeにわたってくるMethodは、ターゲットクラスでなく、プロキシインターフェイスのMethodなので、ターゲットクラスにアノテーションをかける場合は、そのクラスのメソッドをひろってくる必要があります(ここではそうしています)。
AnnotationProxy.java
package test2;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class AnnotationProxy implements InvocationHandler{
private Object target;
public static Object newInstance(Object obj) {
return java.lang.reflect.Proxy.newProxyInstance(
obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(),
new AnnotationProxy(obj));
}
private AnnotationProxy(Object obj) {
this.target = obj;
}
public Object invoke(Object proxy, Method m, Object[] args)
throws Throwable {
Object result;
try {
Method targetMethod = target.getClass().getMethod(m.getName(), (Class[])m.getParameterTypes());
//アノテーションの種類によりあれこれする
Annotation[] annotations = targetMethod.getAnnotations();
for(int i=0; i<annotations.length; i++){
Annotation annotation = annotations[i];
if(annotation.annotationType() == Log.class){
System.out.println("before");
}else{
//...
}
}
result = m.invoke(target, args);
return result;
} catch (InvocationTargetException e) {
throw e.getTargetException();
} catch (Throwable e) {
throw e;
} finally {
}
}
}
次はJavassistを使った例です。クラスロード時にアノテーションを読み込んであれこれしています。
実際にアノテーションをよむところは前の例と同じです。Javassistよく分かってないので、使い方変だったらごめんなさい。
AnnotationedLoader.java
package test3;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
public class AnnotationedLoader extends ClassLoader{
protected synchronized Class< ? > loadClass(String name, boolean resolve)
throws ClassNotFoundException {
Class original = super.loadClass(name, resolve);
if(name.startsWith("java.")){
return original;
}
try{
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get(name);
Method[] methods = original.getMethods();
for(int i=0; i<methods.length; i++){
Method method = methods[i];
Annotation[] annotations = method.getAnnotations();
for(int j=0; j<annotations.length; j++){
Annotation annotation = annotations[j];
if(annotation.annotationType() == Log.class){
CtMethod ctMethod = cc.getDeclaredMethod(method.getName());
ctMethod.insertBefore("System.out.println(\"before\");");
}else{
//...
}
}
}
Class clazz = cc.toClass();
return clazz;
}catch(Exception e){
e.printStackTrace();
throw new ClassNotFoundException();
}
}
}
アスペクトにソースコードメタデータを利用するのは、ちょっちアドホックな方法です。ポイントカットできませんし。まあ、簡単に書けるのは便利なので、アノテーションをサポートしたAOPフレームワークもいくつか出てきているようです。
あと、普通のアプリケーション開発者は自分でアノテーションを書くのでなく、ツール屋さんが作ったアノテーションを利用することの方が多いでしょう。あと、ここでは実行時のアノテーションの例を行いましたが、どっちかというと、コンパイル時などにあれこれするツールの方が主流になってくるかもしれません(いや、よくわからんけど)。
J2SE虎の穴 Metadata は魔法の言葉
http://www5.airnet.ne.jp/sakuraba/java/laboratory/J2SE1.5/LangSpec/Metadata/Metadata.html
dW Tigerでのアノテーション第1回: Javaコードにメタデータを追加する
http://www-6.ibm.com/jp/developerworks/java/041001/j_j-annotate1.pdf
JAVA言語入門 アノテーション
http://wisdom.sakura.ne.jp/programming/java/java5_9.html
JDK5 APIリファレンス
http://java.sun.com/j2se/1.5.0/ja/docs/ja/api/java/lang/annotation/package-summary.html