アノテーションの勉強
スタティック変数アノテーションを利用しやすくするための方法を考えながら、Seasar2の勉強。
J2SE5.0のアノテーションについてちょっと学習。
IBMのdeveloper worksが役だった。
S2Daoってどうやってインターフェースだけなのに処理を実行できてるんだろー。これも調査。
S2DaoInterceptorをみて感動。
Interceptorのすごさがわかった。
なるほど。
よこどりできるってのはこんなこともできるってことなんだ。なんでもありだ。すごい。
DICONファイルを使わないでアスペクトってさせることができるのかなー。
おっ、「diconファイルを使用しないでアスペクトを組み込む方法」ってそのままの記述がある。
簡単にできそう。
なんか作れそうな気がしてきた。
今知ってること
アノテーションは3種類あって
で、アノテーションのターゲットには
- TYPE
- FIELD
- METHOD
- PARAMETER
- CONSTRUCTOR
- LOCAL_VARIABLE
- ANNOTATION_TYPE
- PACKAGE
こんなにあるけど、今必要なのはTYPEとMETHODくらいかなー。
どのアノテーションを適用しているかのきっかけはサフィックスで判断?うーん。これは流れにまかせよう。
スタティック変数アノテーションの種類は、
- 1つのスタティック変数で複数の情報を定義してるアノテーション
public static final String logon_INFO = "name=logonForm, scope=request";
- 複数のスタティック変数で複数の情報を定義してるアノテーション
public static final String logon_VALIDATE = "true"; public static final String logon_ERRFORWARD = "logonError";
- 上記2つの複合型
みたいな感じかなー
とりあえずは1つのスタティック変数で複数の情報を定義しているアノテーションから作ってみよう。
テストはこんなもんかな。
public class ClassAnnotationTest extends TestCase { public void testClassOneFieldAnnotation() { OneFieldAnnotation annotation = (OneFieldAnnotation) AnnotationUtil .get(OneField.class, OneFieldAnnotation.class); assertNotNull(annotation); assertEquals("testForm", annotation.name()); assertEquals("request", annotation.scope()); assertEquals(true, annotation.validate()); } }
public interface OneFieldAnnotation { public static final String TARGET = "TYPE"; public static final String SUFFIX = "ACTION"; public String name(); public String scope(); public boolean validate(); }
public class OneField { public static final String[] ACTION = { "name=testForm", "scope=request", "validate=true" }; }
Eclipseの力でコンパイルを通してテスト実行。そして失敗。
グリーンバーにするためにAnnotatilUtilを作成
public class AnnotationUtil { private static final String SUFFIX = "SUFFIX"; public static Object get(Class clazz, Class annClazz) { BeanDesc annBeanDesc = BeanDescFactory.getBeanDesc(annClazz); Field annField = annBeanDesc.getField(SUFFIX); String suffix = (String) FieldUtil.get(annField, null); BeanDesc beanDesc = BeanDescFactory.getBeanDesc(clazz); if (!beanDesc.hasField(suffix)) { return null; } Field field = beanDesc.getField(suffix); Map params = AnnotationConfigUtil.getParameterMap(field); Aspect aspect = new AspectImpl(new ClassAnnotationInterceptor(params)); AopProxy aopProxy = new AopProxy(annClazz, new Aspect[] { aspect }); return aopProxy.create(); } }
あと、ClassAnnotationInterceptorも
public class ClassAnnotationInterceptor extends AbstractInterceptor { private Map params; public ClassAnnotationInterceptor(Map params) { this.params = params; } public Object invoke(MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); if (!MethodUtil.isAbstract(method)) { return invocation.proceed(); } Object ret = params.get(method.getName()); Class retType = method.getReturnType(); if (retType.isPrimitive()) { return NumberConversionUtil.convertPrimitiveWrapper(retType, ret); } else if (Number.class.isAssignableFrom(retType)) { return NumberConversionUtil.convertNumber(retType, ret); } return ret; } }
こんな感じ。ほとんどをS2DaoInterceptorの真似をして作った。
テスト実行。グリーンバー
だけど、うれしくないなー
やる気がないせいか・・・
テストを作りながらやる気をだそうかな。
簡単なテストからしてみるか。
public void testNotDefineForClass() { // 対象となるアノテーションインターフェースが定義されてない場合、 // nullが戻ってくることを確認。 NotDefineAnnotation annotation = (NotDefineAnnotation) AnnotationUtil .get(OneField.class, NotDefineAnnotation.class); assertNull(annotation); } public void testNotDefineSuffix() { // AnnotationインターフェースにSUFFIXが定義されていない場合、 // インタフェース名をSUFFIXとして処理を行う。 NotDefineSuffixAnnotation annotation = (NotDefineSuffixAnnotation) AnnotationUtil .get(NotDefineSuffix.class, NotDefineSuffixAnnotation.class); assertNotNull(annotation); assertEquals("testName", annotation.name()); }
これは通った。
TARGETがTYPE以外の場合、nullがかえってくるようにしよう。定義されていない場合は・・・きちんとインターフェイスを実装して返してあげよう。
public void testIllegalTarget() { // AnnotationインターフェースのTARGETがTYPE以外の場合、 // nullが戻ってくることを確認。 IllegalTargetAnnotation annotation = (IllegalTargetAnnotation) AnnotationUtil .get(OneField.class, IllegalTargetAnnotation.class); assertNull(annotation); } public void testNotDefineTarget() { // AnnotationインターフェースにTARGETが定義されていない場合でも // Annotationのターゲットとなっていることを確認 NotDefineTargetAnnotation annotation = (NotDefineTargetAnnotation) AnnotationUtil .get(OneField.class, NotDefineTargetAnnotation.class); assertNotNull(annotation); }
次は、マーカー・アノテーションとか単一値アノテーションのテスト
public void testMarkerAnnotation() { OneFieldMarkerAnnotation annotation = (OneFieldMarkerAnnotation) AnnotationUtil .get(OneField.class, OneFieldMarkerAnnotation.class); assertNotNull(annotation); } public void testSingleValueAnnotation() { OneFieldSingleValueAnnotation annotation = (OneFieldSingleValueAnnotation) AnnotationUtil .get(OneField.class, OneFieldSingleValueAnnotation.class); assertNotNull(annotation); assertEquals("test", annotation.value()); } public void testFullAnnotation() { OneFieldFullAnnotation annotation = (OneFieldFullAnnotation) AnnotationUtil .get(OneField.class, OneFieldFullAnnotation.class); assertNotNull(annotation); assertEquals("testForm", annotation.name()); assertEquals("request", annotation.scope()); assertEquals(true, annotation.validate()); }
その前につくっていたtestClassOneFieldAnnotation()はtestFullAnnotation()に名前を変えてみた。
こっちのほうがしっくりくるし。
あとAnnotationUtil#get()の処理内容も修正して、ClassAnnotationInterceptorをFullAnnotationInterceptor、SingleValueAnnotationInterceptorの2つにわけた。
public class AnnotationUtil { private static final String SUFFIX = "SUFFIX"; private static final String TARGET = "TARGET"; private static final String TARGET_TYPE = "TYPE"; public static Object get(Class clazz, Class annClazz) { BeanDesc annBeanDesc = BeanDescFactory.getBeanDesc(annClazz); if (!isTypeAnnotation(annBeanDesc)) { return null; } String suffix = getAnnotationSuffix(annBeanDesc); BeanDesc beanDesc = BeanDescFactory.getBeanDesc(clazz); if (!beanDesc.hasField(suffix)) { return null; } Field field = beanDesc.getField(suffix); if (field.getType().isArray()) { Map params = AnnotationConfigUtil.getParameterMap(field); Aspect aspect = new AspectImpl( new FullAnnotationInterceptor(params)); AopProxy aopProxy = new AopProxy(annClazz, new Aspect[] { aspect }); return aopProxy.create(); } else { String value = AnnotationConfigUtil.getParameter(field); Aspect aspect = new AspectImpl( new SingleValueAnnotationInterceptor(value)); AopProxy aopProxy = new AopProxy(annClazz, new Aspect[] { aspect }); return aopProxy.create(); } } private static boolean isTypeAnnotation(BeanDesc beanDesc) { if (beanDesc.hasField(TARGET)) { Field field = beanDesc.getField(TARGET); String target = (String) FieldUtil.get(field, null); return TARGET_TYPE.equals(target); } return true; } private static String getAnnotationSuffix(BeanDesc beanDesc) { if (beanDesc.hasField(SUFFIX)) { Field field = beanDesc.getField(SUFFIX); return (String) FieldUtil.get(field, null); } else { return ClassUtil.getShortClassName(beanDesc.getBeanClass()) .toUpperCase(); } } }
次はデフォルト値の適用。
テストはこんな感じで
public void testDefaultValue() { DefaultValueAnnotation annotation = (DefaultValueAnnotation) AnnotationUtil .get(OneField.class, DefaultValueAnnotation.class); assertNotNull(annotation); assertEquals("/test/path", annotation.path()); }
アノテーションインターフェースは
public interface DefaultValueAnnotation { public static final String TARGET = "TYPE"; public static final String SUFFIX = "FULL"; public String name(); public String scope(); public boolean validate(); public static final String path_DEFAULT = "/test/path"; public String path(); }
これでいいと思う。なんかアノテーションインターフェースの中にアノテーションがあるって雰囲気になるのかなー。
とりあえず、テストを実行して失敗することを確認したけど、これを作る前にメソッドごとのアノテーションを先につくることにしよう。
いまさら、思ったんだけどマーカー・アノテーションって、空のインターフェースを明確にしたものって感じなのかな・・・・もしかして。
ってことは、作っても使う用途がないかも・・・。
テストから削除。
それで、メソッドごとのアノテーションの実装に入ろう。最初のテストは
public class MethodAnnotationTest extends TestCase { public void testSingleValueAnnotation() { Method method = OneFieldMethod.class.getDeclaredMethods()[0]; OneFieldSingleValueAnnotation annotation = (OneFieldSingleValueAnnotation) AnnotationUtil .get(method, OneFieldSingleValueAnnotation.class); assertNotNull(annotation); assertEquals("test", annotation.value()); } }
で、アノテーションインターフェース(タイプ?)は
public interface OneFieldSingleValueAnnotation { public static final String TARGET = "METHOD"; public static final String SUFFIX = "SINGLEVALUE"; public String value(); }
で、アノテーションをつけてるクラスは
public class OneFieldMethod { public static final String getSingleValue_SINGLEVALUE = "test"; public String getSingleValue() { return "singleValue"; } }
こんな感じ。Eclipseの力でコンパイルを通してテスト実行。そして失敗。
次はグリーンバーにする作業だけど、これまたAnnotationUtilを修正しないといけないなー。
public class AnnotationUtil { private static final String SUFFIX = "SUFFIX"; private static final String TARGET = "TARGET"; private static final String TARGET_TYPE = "TYPE"; private static final String TARGET_METHOD = "METHOD"; public static Object get(Class clazz, Class annClazz) { BeanDesc annBeanDesc = BeanDescFactory.getBeanDesc(annClazz); if (!isTypeAnnotation(annBeanDesc)) { return null; } String suffix = getAnnotationSuffix(annBeanDesc); BeanDesc beanDesc = BeanDescFactory.getBeanDesc(clazz); return createAnnotationObject(beanDesc, annClazz, suffix); } private static boolean isTypeAnnotation(BeanDesc beanDesc) { if (beanDesc.hasField(TARGET)) { Field field = beanDesc.getField(TARGET); String target = (String) FieldUtil.get(field, null); return TARGET_TYPE.equals(target); } return true; } // ----------------------------------------------------------------------- public static Object get(Method method, Class annClazz) { BeanDesc annBeanDesc = BeanDescFactory.getBeanDesc(annClazz); if (!isMethodAnnotation(annBeanDesc)) { return null; } String suffix = method.getName() + "_" + getAnnotationSuffix(annBeanDesc); BeanDesc beanDesc = BeanDescFactory.getBeanDesc(method .getDeclaringClass()); return createAnnotationObject(beanDesc, annClazz, suffix); } private static boolean isMethodAnnotation(BeanDesc beanDesc) { if (beanDesc.hasField(TARGET)) { Field field = beanDesc.getField(TARGET); String target = (String) FieldUtil.get(field, null); return TARGET_METHOD.equals(target); } return true; } // ----------------------------------------------------------------------- private static String getAnnotationSuffix(BeanDesc beanDesc) { if (beanDesc.hasField(SUFFIX)) { Field field = beanDesc.getField(SUFFIX); return (String) FieldUtil.get(field, null); } else { return ClassUtil.getShortClassName(beanDesc.getBeanClass()) .toUpperCase(); } } private static Object createAnnotationObject(BeanDesc beanDesc, Class annClazz, String suffix) { if (!beanDesc.hasField(suffix)) { return null; } Field field = beanDesc.getField(suffix); if (field.getType().isAssignableFrom(String[].class)) { Map params = AnnotationConfigUtil.getParameterMap(field); Aspect aspect = new AspectImpl( new FullAnnotationInterceptor(params)); AopProxy aopProxy = new AopProxy(annClazz, new Aspect[] { aspect }); return aopProxy.create(); } else { Object value = AnnotationConfigUtil.getParameter(field); Aspect aspect = new AspectImpl( new SingleValueAnnotationInterceptor(value)); AopProxy aopProxy = new AopProxy(annClazz, new Aspect[] { aspect }); return aopProxy.create(); } } }
こんな感じに修正してテスト実行したら、グリーンバーにはなったけど・・・きたない。
そして、ふーんって感じ。クラスに対するアノテーションとメソッドに対するアノテーションは、SUFFIXを生成する場所以外はほぼ同じだ。
うまくやればもっといい感じで作れるのかも。
グリーンバーになることはわかってるんだけど、メソッドごとのアノテーションにテストを追加していくか。
やっと追加が終わった。結構つかれた・・・
これでアノテーションインタフェース(タイプ?)のデフォルト値適用に臨める。
これは、FullAnnotationInterceptorを改造すればグリーンバーにできそうだなー。
こんな感じに直して
public class FullAnnotationInterceptor extends AbstractAnnotationInterceptor { private Map params; public FullAnnotationInterceptor(Map params) { this.params = params; } public Object invoke(MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); if (!MethodUtil.isAbstract(method)) { return invocation.proceed(); } Object ret = null; if (params.containsKey(method.getName())) { ret = params.get(method.getName()); } else { DefaultValueAnnotation annotation = (DefaultValueAnnotation) AnnotationUtil .get(method, DefaultValueAnnotation.class); if (annotation == null) { return null; } ret = annotation.value(); } return convertReturnValue(method, ret); } }
DefaultValueAnnotationを追加すれば、
public interface DefaultValueAnnotation { public static final String TARGET = "METHOD"; public static final String SUFFIX = "DEFAULT"; public Object value(); }
グリーンバーになった。
デフォルト値適用のテストは、フル・アノテーションのときのみだから、単一値アノテーションについてもテストを作って実装しないと。
作ってて思ったんだけど、単一値アノテーションのデフォルト値って使うのかなーってちょっと思った。
とりあえず、作っておこう。
まだまだ機能は足りないけど、一段落ついたかな。
きたない部分が増えてるから、ちょっと見直しをしよう。