CDI(旧名WebBeans)入門 その4
の続き。
今回はAOP。
といってもEJB3のAOPと同じ。EJB3のときもアノテーションをEJBパッケージにしないで汎用的なパッケージにしていたのでそのまま使う。
つまり、CDIとはPOJOへEJBの便利な機能を徐々に付与していくためのものと考えるといいかもしれない。
まずは基礎となるハローサービス
package hello; import java.io.IOException; import java.io.PrintWriter; import javax.inject.Inject; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet(name="HelloServlet", urlPatterns={"/HelloServlet"}) public class HelloServlet extends HttpServlet { @Inject Hello hello; @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); try { out.println("<html>"); out.println("<head>"); out.println("<title>Servlet HelloServlet</title>"); out.println("</head>"); out.println("<body>"); out.println("<h1>" + hello.getMessage("WebBeans") + "</h1>"); out.println("</body>"); out.println("</html>"); } finally { out.close(); } } }
続いて注入されるサービスクラス。
package hello; import javax.enterprise.context.RequestScoped; @RequestScoped public class Hello { public String getMessage(String name){ return "はろー"+name; } }
実行結果は予想通り「はろーWebBeans」と表示される。
これにAOPを入れてみる。メソッドの実行時間を計測するやつにしよう。
AOP適用
まずはアノテーション。
package hello; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import javax.interceptor.InterceptorBinding; @InterceptorBinding @Retention(RetentionPolicy.RUNTIME) @Inherited @Target({ElementType.METHOD,ElementType.TYPE}) public @interface TimeLog { }
続いてインターセプタ。
package hello; import java.util.logging.Level; import java.util.logging.Logger; import javax.interceptor.AroundInvoke; import javax.interceptor.Interceptor; import javax.interceptor.InvocationContext; @TimeLog @Interceptor public class HelloInterceptor { @AroundInvoke public Object はさみこむぜ(InvocationContext ic){ Object result; try { long start = System.nanoTime(); result = ic.proceed(); long end = System.nanoTime(); System.out.println(String.format("時間%,d ns" , end-start)); return result; } catch (Exception ex) { Logger.getLogger(HelloInterceptor.class.getName()).log(Level.SEVERE, null, ex); } return null; } }
そして適用するサービスへ最初に作ったアノテーションをつける。
@RequestScoped @TimeLog public class Hello {
最後に設定ファイルに記述。
<beans xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd"> <interceptors> <class>hello.HelloInterceptor</class> </interceptors> </beans>
これでメソッド時学校されるたびに時間がログへ表示される。
まずTimeLogアノテーションはメソッドにも使用できるようにしてある。今回はクラスレベルで指定しているが、これでメソッド単位でAOPを入れたり指定することが可能だ。
アノテーションは@InterceptorBindingをつける。これがAOPを設定するためのもの。
難しくは無いよね。
さらに面白いのを。
ライフサイクルメソッドにもAOPが適用できる
インターセプタークラスへ以下の行を追加。
@PostConstruct public void init(InvocationContext ic) throws Exception{ System.out.println("初期化処理前"+ic.getTarget()); ic.proceed(); System.out.println("初期化処理後"+ic.getTarget()); } @PreDestroy public void destroy(InvocationContext ic) throws Exception{ System.out.println("破棄処理前"+ic.getTarget()); ic.proceed(); System.out.println("破棄処理前"+ic.getTarget()); }
実行
情報: Loading application JavaEE6Test at /JavaEE6Test 情報: JavaEE6Test was successfully deployed in 1,312 milliseconds. 情報: 初期化処理前hello.Hello@f9ccb8 情報: 初期化処理後hello.Hello@f9ccb8 情報: 時間23,225 ns 情報: 破棄処理前hello.Hello@f9ccb8 情報: 破棄処理前hello.Hello@f9ccb8
ふむ。ちゃんとよばれてるね。proceedメソッドが何もしていないので挟み込むこと自体は何の意味も無いが、終了のタイミングでいろいろと後片付けが出来るのでかなりよろしい。
サービスクラスにライフサイクルメソッドを実装してみる。以下を追加する。
@PostConstruct void init(){ System.out.println("初期化"); } @PreDestroy void destroy(){ System.out.println("破棄"); }
実行
情報: Loading application JavaEE6Test at /JavaEE6Test 情報: JavaEE6Test was successfully deployed in 1,328 milliseconds. 情報: 初期化処理前hello.Hello@1aa4854 情報: 初期化 情報: 初期化処理後hello.Hello@1aa4854 情報: 時間24,021 ns 情報: 破棄処理前hello.Hello@1aa4854 情報: 破棄 情報: 破棄処理前hello.Hello@1aa4854
ちゃんと挟み込まれているのがわかると思う。
これでコンテナ管理のトランザクションも簡単に実装できるのがわかると思う。UserTransaction/TransactionManagerを@Resourceでインターセプターに注入すればすぐに仕える。