作成 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