CDI(旧名WebBeans)入門 その4

の続き。

今回はAOP

といってもEJB3AOPと同じ。EJB3のときもアノテーションEJBパッケージにしないで汎用的なパッケージにしていたのでそのまま使う。

つまり、CDIとはPOJOEJBの便利な機能を徐々に付与していくためのものと考えるといいかもしれない。

まずは基礎となるハローサービス

サーブレット

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でインターセプターに注入すればすぐに仕える。