アノテーションを用いて既存クラスの活躍場所を増やす

アノテーションの勉強をしました。とは言え勉強の範囲は非常に狭く、実行時にアノテーションを解釈して、実行時に振る舞いを変えるにはどうすればいいか?というレベルです。Javaコンパイラが使用するアノテーション(@Overrideなど)は対象外です。

アノテーションについては、以下の記事が参考になります。
http://journal.mycom.co.jp/column/java/026/index.html


アノテーションを用いることで、ある既存クラスにアノテーションを付与することで、既存クラスに別の仕事をさせることが容易になりました。あまりrailsを知らないけれど、railsのact_as_*に近いのかな?

例えば、あるクラスが保持するデータを何らかの検索システムで検索したい、というような要望があったとします。
その場合、ぱっと思いつくのは以下のようなアノテーションがあればいいのかなと。

まずは最初の「検索可能である」というアノテーションを作ります。

@Retention(RetentionPolicy.RUNTIME)  //実行時に使用可能
@Target(ElementType.TYPE)  //クラスに対する注釈
public @interface Searchable {
}

次に、「何の値で検索するか」のアノテーションを作ります。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)   //メソッドに対する注釈
public @interface SearchField {
    String option() default "";  //option引数。省略時は空文字。
}

最後に、上記アノテーションを処理するクラスを作ります。

class SearchEngine {
    public static SearchResult search(Object searchable) throws Exception {
        //Searchableアノテーションのないクラスは検索不可
        if (!searchable.getClass().isAnnotationPresent(Searchable.class)) {
            //例外の送出
        }

        try {
            //各メソッドが持つアノテーションを調べ、検索関係のアノテーションを発見したら
            //検索条件を作成する
            List<SearchCondition> searchConditions = new LinkedList<SearchCondition>();
            for (Method method : searchable.getClass().getMethods()) {
                if (method.isAnnotationPresent(SearchField.class)) {
                    //SearchFieldアノテーションが付いたメソッドを呼んで検索条件を取得する
                    String query = (String) method.invoke(searchable, new Object[0]);
                    //SearchFieldアノテーションのoption引数の値を取得する
                    String option = method.getAnnotation(SearchField.class).option();

                    SearchCondition cond = new SearchCondition();
                    cond.setQuery(query);
                    cond.setOption(option);
                    searchConditions.add(cond);
                }
            }
            //あとは検索処理をこの辺に書く
            return .....
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
    }
}

こんなのを作れば、例えばProductの製品名で検索を要求したい場合、以下のようにアノテーションを付与し、SearchEngine.search() に渡せばOK。この場合、Product.getName()の値で、ignoreCaseオプションつきで検索する、というイメージです。

@Searchable
public class Product {
	@SearchField(option="ignoreCase")
	public String getName() { //...
	}
}

アノテーションは実行時に解釈する処理用のアノテーションであれば、実装は思ったほど難しくありません。コンパイラだけが使用するアノテーションなどは難しいのかもしれませんが、実行時用のアノテーションは使いどころが色々ありそうなので、勉強してみる価値はありそうです。

今はXMLXPath指定でクラスにマッピングさせるようなライブラリが欲しいのですが、なかなか難しく、ちょっと中断気味 orz