はじめてのCDI(旧名WebBeans)

http://d.hatena.ne.jp/Hirohiro/20091228/1262013022

ここ見て思い出した。そういやJava EE 6でWebBeansもきてるんだった。

個人的にはCDIという名前は好きではない。正式名称にしても覚えにくいし、検索が非常にしにくい。WebBeansは検索が容易だったのに。固有名詞は大事だよと。

というわけでWebBeansを軽く。

ただし、通常のJavaEE管理下のオブジェクトのInject注入だけってのはいろんなところにサンプルがあるし面白くないので、既存のフレームワークとの連携を考える場合で記述してみる。今回は説明しないが、WebBeansはすでにインスタンスが生成されたものも注入したり柔軟性は意識している。

まずはサーブレットからリクエストスコープのオブジェクトを取得、そリクエストスコープのオブジェクトにシングルトンのオブジェクトが注入してある、といったケースを説明する。

今回も動作はカウントアップ。ただし、カウンタの値部分はシングルトンで保存してあるというもので、フロントエンドはリクエストスコープというわざとスコープを変えてある。


StrutsT2Frameworkならば全てシングルトン設定、もしくはアプリケーションスコープでいいだろう。

今回も省略なしで全ての設定ファイル、コードを記述している。

環境設定ファイル

中身は空っぽでいいので設定ファイルをWEB-INF直下にbeans.xmlという名前でおいておく必要がある。

<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">

</beans>

カウンタ保持Bean

名前をつけているけど今回は利用していないのでなくてもよい。

import javax.inject.Named;
import javax.inject.Singleton;

@Named("かうんた")
@Singleton
public class CounterHolder {
    private int count = 0;

    public synchronized int next() {
        return count ++;
    }
}

リクエストスコープのサーブレットから呼び出されるBean

import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;


@RequestScoped
@Named("うぇぶ")
public class HogeBean {

    //こいつ注入できるぞ
    @Inject
    CounterHolder holder;

    public String getCounter(){
        return holder.next()+"回目の訪問者です";
    }
}

先ほどのBeanを注入してある。

サーブレット

import java.io.IOException;
import java.io.PrintWriter;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.naming.InitialContext;
import javax.naming.NamingException;
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="HogeServlet", urlPatterns={"/HogeServlet"})
public class HogeServlet extends HttpServlet {


    @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 HogeServlet</title>");
                out.println("</head>");
                out.println("<body>");

                try {
                    BeanManager bm = InitialContext.doLookup("java:comp/BeanManager");
                    
                    for(Bean b : bm.getBeans("うぇぶ")){//名前で検索(Setでかえるのでループ)
                        CreationalContext cc = bm.createCreationalContext(null);

                        HogeBean wb = (HogeBean) b.create(cc);
                        out.println(wb.getCounter());
                        out.println("<br>");
                        out.println(wb);//requestScopeでインスタンスが毎回変わるかどうかの確認
                    }

                } catch (NamingException ex) {
                    Logger.getLogger(HogeServlet.class.getName()).log(Level.SEVERE, null, ex);
                }
                out.println("</body>");
                out.println("</html>");
        } finally { 
            out.close();
        }
    } 
}

ポイントはBeanManager。

マッピングアノテーションで設定してあるのでweb.xmlは必要としていない。

実行

0回目の訪問者です
HogeBean@1fa5cc0

1回目の訪問者です 
HogeBean@1372611  

…

リクエストごとにインスタンスがかわっているのがわかると思う。カウンタはシングルトンなので全てのブラウザからのアクセスで共通している、と。

JSFサーブレットなどの場合@Injectで注入できるのだが、他のフレームワークとの連携ならばこういったアクセスは必要になるだろうから備忘録のために残しておく。

さらにおもしろいのが

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
   "http://www.w3.org/TR/html4/loose.dtd">

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JSP Page</title>
    </head>
    <body>
        ${うぇぶ.counter}
    </body>
</html>

こういったJSPを作成してアクセスをするとちゃんと「n回目の訪問者です」と表示される。
request#setAttributeで値を与えてあげる必要は無い。

もともとJSFEJBをELで結びつけるというものであったが、request#setAttributeを必要としないのでStrutsと組みあわせるのも面白い気がした。ELでシンプルに結びつけるというのはJSFのキモだが、それが他のフレームワークでも恩恵を得るということ。Cubbyもわるくないんじゃないかな。おそらくStrutsとは思えない開発スタイルになると思う。後で軽くサンプルを作ってみよう。


こうしてみると、Guice形式の基本的な注入がAPIとして標準化されたという意義はやはり大きいかも。