- CDI(旧名WebBeans)入門 その1
- CDI(旧名WebBeans)入門 その2
- CDI(旧名WebBeans)入門 その3
- CDI(旧名WebBeans)入門 その4
- CDI(旧名WebBeans)入門 その5
の続き。
今回はプロデューサーメソッドについて。
Google Guiceを知っている人ならプロバイダーメソッド(@Provides)と同じようなものと考えるとわかりやすい。
たとえばデータベースのコネクションを取得するコードを書いてみる。
package produce; import java.sql.Connection; import java.sql.SQLException; import javax.annotation.Resource; import javax.enterprise.context.RequestScoped; import javax.enterprise.inject.Produces; import javax.inject.Named; import javax.inject.Singleton; import javax.sql.DataSource; @Singleton public class ConnectionService { @Resource(name="jdbc/sample") DataSource ds; @Named("こねくしょん") @Produces @RequestScoped public Connection getConnection() throws SQLException{ Connection con = ds.getConnection(); return con; } }
これでコネクションがリクエストスコープで取得できる。
では早速これを使うサーブレットのサンプルを。
package produce; import java.io.IOException; import java.io.PrintWriter; import java.sql.Connection; import java.sql.ResultSet; import javax.inject.Inject; import javax.inject.Named; 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="DBServlet", urlPatterns={"/DBServlet"}) public class DBServlet extends HttpServlet { @Named("こねくしょん") @Inject Connection con; @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 DBServlet</title>"); out.println("</head>"); out.println("<body>"); out.println(con+"<br>"); //テーブル一覧取得 ResultSet rs = con.getMetaData().getTables(null, null, null, new String[]{"TABLE"}); while(rs.next()){ out.println(rs.getString("TABLE_NAME") +"<br>"); } rs.close(); con.close(); out.println("</body>"); out.println("</html>"); }catch(Exception ex){ ex.printStackTrace(); } finally { out.close(); } } }
これでコネクションのインスタンスの名前とテーブル一覧が表示される。
ただし、終了時の処理が出来ていない。上の例でもcloseを呼び出した側がやっている。
今まで覚えた方法だとConnectionのラッパクラスを作ってライフサイクルアノテーション(@PreDestroy)でCloseするという処理をすることで可能にはなる。
だがそれはあまりにも面倒だ。
というわけでそれに対応する。コネクション生成側を以下のように修正する。
package produce; import java.sql.Connection; import java.sql.SQLException; import javax.annotation.Resource; import javax.enterprise.context.RequestScoped; import javax.enterprise.inject.Disposes; import javax.enterprise.inject.Produces; import javax.inject.Named; import javax.inject.Singleton; import javax.sql.DataSource; @Singleton public class ConnectionService { @Resource(name="jdbc/sample") DataSource ds; @Named("こねくしょん") @Produces @RequestScoped public Connection getConnection() throws SQLException{ Connection con = ds.getConnection(); return con; } public void closeConnection(@Named("こねくしょん") @Disposes Connection con){ try { if(con.isClosed()){ System.out.println("もうくろーずされてた(T_T) :" + con); return; } System.out.println("くろーずするよ(^_^) :" + con); con.close(); } catch (SQLException ex) { ex.printStackTrace(); } } }
引数に@Producesを指定したものを書いたメソッドを用意、かわりに@Disposesを記述。するとライフサイクルが終わる直前にこのメソッドが呼び出される。
呼び出し側でのcloseをコメント化してみたりして動きを確認するとよい。
さて、サーブレットのコードが今までのイメージとまったく変わったのがわかるでしょうか。データソースではなく、コネクションそのものを注入しているということ。そしてラッパクラスを作らずとも後始末もやってくれるということ。
これによって安全なコードが多少は書きやすくなるかもしれない。もちろん、リクエストスコープでは生存期間が長すぎるという場合もあるとは思うが、このへんのバランスとるのは面白いかもしれない。
だってInputStraemとかまちがってコネクション閉じ忘れ(誰が閉じるべきかを明示しないとどうしようもないリソースなのでドキュメントに書いておかないと実は結構使いにくいのはご存知の通り)してもちゃんと閉じてくれるんですよ。その場合スコープは呼び出し側の依存スコープにしておくといいかも。
つまり、ロジックだけだからシングルトンでいいや的なのはやめて、積極的にスコープをもつのがCDIらしい開発なのかもしれない。フィールド変数とか注入とか使いやすくなるしね。シングルトン前提だとこのメソッドだけ実装してすごい大きくなったり、メソッド分割してもすごい引数の数が多いとかそういうのが増えてしまいがちだし。