新トップページへ | Tip

CDIを使う

LastUpdate : 12/11/03

Java EE6となり、CDIの仕様が導入されました。
DI機能が標準仕様となりました。さらに、1歩踏み込み、コンテキストの概念が追加されました(コンテキストの頭のCが追加されて、CDIという名前になったみたい)。
CDIの基本的な使い方のサンプルをこのページでは扱います。

DIの説明については端折ります。ただ、ほかのDIコンテナと違うのは、DI出来るのは、コンテナ上で動いている場合のみ(WebコンテナやEJBコンテナ)です。なので、サーブレット上で、サンプルプログラムの動作確認をしています。

また、CDI機能を使う場合、「beans.xml」ファイルをWEB-INFディレクトリ直下に作成する必要があります。このファイルがあるかないかで、CDIを有効・無効の切り替えを行います。このファイルはEclipseでは自動で生成できます(CDIを使うオプションをONにすれば良い)


もくじ

セッションスコープ(@SessionScoped)とリクエストスコープ(@RequestScoped)のDI
カンバセーションスコープ(@ConversationScoped)のDI
DIするオブジェクトが、判別できない場合、アノテーションでどのインスタンスをDIするかを指定するサンプル(@Qualifierの使い方)
@Producesの使い方
インターセプターの使い方
Decoratorの使い方
「@Alternative」の使い方について


セッションスコープ(@SessionScoped)とリクエストスコープ(@RequestScoped)のDI

DIするインスタンスの生存期間が、リクエストスコープのものとセッションスコープのもののサンプルです。あともう一つカンバセーションスコープがありますが、それはでやります。

以下のような画面遷移をするサンプルアプリケーションを作成しました。このアプリケーションのソースを示します。
当サンプルのプロジェクト一式を置いておきます。

1番目の画面




2番目の画面




3番目の画面




最初に戻る

画面のrequestscopedComponentという値は、@RequestScopeのオブジェクトから値を取得しています。同じく。sessionScopedComponentという値は@SessionScopeのオブジェクトから値を取得しています。

上記アプリは単純さサーブレットで作られています。サーブレットから以下に示すサービスクラスをDIして、出力する値を取得しています。

CDIStudy1Service.java
package alctail.service;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;

import alctail.component.CDIStudy1RequestScopedComponent;
import alctail.component.CDIStudy1SessionScopedComponent;

@RequestScoped
public class CDIStudy1Service {

        @Inject
        private CDIStudy1RequestScopedComponent requestScopedComponent;
        @Inject
        private CDIStudy1SessionScopedComponent sessionScopedComponent;
        
        public String getMsg() {
                return  "requestScopedComponent : "+requestScopedComponent.getMsg()+"<br>"+
                                "sessionScopedComponent : "+sessionScopedComponent.getMsg()+"<br>";
        }
        
        @PostConstruct
        public void init() {
                System.out.println(">>>>> CDIStudy1Serviceインスタンス作成");
        }
        @PreDestroy
        public void preDestroy() {
                System.out.println("<<<<< CDIStudy1Serviceインスタンス破棄");
        }
}

DIしている〜〜ScopedComponentのソースを以下に示します。

CDIStudy1RequestScopedComponent.java
package alctail.component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.enterprise.context.RequestScoped;

@RequestScoped
public class CDIStudy1RequestScopedComponent {
        
        private int num = 0;
        
        public String getMsg() {
                num++;
                return "ふひひ : "+num;
        }
        
        @PostConstruct
        public void init() {
                System.out.println(">>>>> CDIStudy1RequestScopedComponentインスタンス作成");
        }
        @PreDestroy
        public void preDestroy() {
                System.out.println("<<<<< CDIStudy1RequestScopedComponentインスタンス破棄");
        }

}


CDIStudy1SessionScopedComponent.java
package alctail.component;

import java.io.Serializable;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.enterprise.context.SessionScoped;

/**
 * 「@SessionScoped」の場合、 Serializableをimplementsしないと、デプロイ時にエラーとなる。
 */
@SessionScoped
public class CDIStudy1SessionScopedComponent implements Serializable {
        
        private int num = 0;
        
        public String getMsg() {
                num++;
                return "ふひひ : "+num;
        }
        
        @PostConstruct
        public void init() {
                System.out.println(">>>>> CDIStudy1SessionScopedComponentインスタンス作成");
        }
        @PreDestroy
        public void preDestroy() {
                System.out.println("<<<<< CDIStudy1SessionScopedComponentインスタンス破棄");
        }

}

ソースを見るとわかりますが、すべてアノテーションを付与しているだけです。
また、セッションスコープのものは、Serializableをimplementsしないとエラーになるので注意です。

上記ソースを以下のようなサーブレットのソースを作成し、画面を表示しています。

CDIStudy1First.java
package alctail.servlet;

import java.io.PrintWriter;

import javax.inject.Inject;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import alctail.component.CDIStudy1ConversationScopedComponent;
import alctail.service.CDIStudy1Service;


@WebServlet(urlPatterns={"/CDIStudy1First"})
public class CDIStudy1First extends HttpServlet {
        
        @Inject
        private CDIStudy1Service service;
        
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) {
                // テキストを返すようにする
                response.setContentType("text/html;charset=UTF-8");

                String nextAddress = "http://localhost:8080/CDIStudy1/CDIStudy1Second";
                
                PrintWriter writer = null;
                try {
                        writer = response.getWriter();
                        writer.println("<html><body>");
                        writer.println("<p>"+"最初の画面"+"</p>");
                        writer.println("<p>"+service.getMsg()+"</p>");
                        writer.println("<a href=\""+nextAddress+"\">"+nextAddress+"</a>");
                        writer.println("</body></html>");
                } catch(Exception e) {
                        e.printStackTrace();
                }
        }
}


CDIStudy1Second.java
package alctail.servlet;

import java.io.PrintWriter;

import javax.inject.Inject;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import alctail.service.CDIStudy1Service;


@WebServlet(urlPatterns={"/CDIStudy1Second"})
public class CDIStudy1Second extends HttpServlet {
        
        @Inject
        private CDIStudy1Service service;
        
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) {
                // テキストを返すようにする
                response.setContentType("text/html;charset=UTF-8");
                
                String nextAddress = "http://localhost:8080/CDIStudy1/CDIStudy1Third";
                
                PrintWriter writer = null;
                try {
                        writer = response.getWriter();
                        writer.println("<html><body>");
                        writer.println("<p>"+"2番目の画面"+"</p>");
                        writer.println("<p>"+service.getMsg()+"</p>");
                        writer.println("<a href=\""+nextAddress+"\">"+nextAddress+"</a>");
                        writer.println("</body></html>");
                } catch(Exception e) {
                        e.printStackTrace();
                }
        }
}


CDIStudy1Third.java
package alctail.servlet;

import java.io.PrintWriter;

import javax.inject.Inject;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import alctail.service.CDIStudy1Service;


@WebServlet(urlPatterns={"/CDIStudy1Third"})
public class CDIStudy1Third extends HttpServlet {
        
        @Inject
        private CDIStudy1Service service;
        
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) {
                // テキストを返すようにする
                response.setContentType("text/html;charset=UTF-8");
                
                String nextAddress = "http://localhost:8080/CDIStudy1/CDIStudy1First";
                
                PrintWriter writer = null;
                try {
                        writer = response.getWriter();
                        writer.println("<html><body>");
                        writer.println("<p>"+"最後の画面"+"</p>");
                        writer.println("<p>"+service.getMsg()+"</p>");
                        writer.println("<a href=\""+nextAddress+"\">"+nextAddress+"</a>");
                        writer.println("</body></html>");
                } catch(Exception e) {
                        e.printStackTrace();
                }
        }
}


カンバセーションスコープ(@ConversationScoped)のDI

このカンバセーションスコープは、画面と一緒につかわないと動かないみたいです(やってみた感じ、そんなんだった)。なので、今回はFaceletsで作った画面アプリです。
ソース上、スタート処理を呼び、ソース上で終了処理を呼ぶまでの期間が、生存期間のセッションスコープみたいなものです。

まず、DIするクラスのソースです。
ポイントとしては、Conversationのオブジェクトをフィールドに定義し、それに対し@Injectをつけていることです。あとは、カンバセーションスコープを示すアノテーションをつけているぐらいで、それ以外はPOJOと変わりません。
今回もプロジェクト一式を置いておきます。

ConversationScopedComponenta.java
package alctail.cdi;

import java.io.Serializable;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.enterprise.context.Conversation;
import javax.enterprise.context.ConversationScoped;
import javax.inject.Inject;
import javax.inject.Named;

/** ConversationScopedのサンプル */
@Named
@ConversationScoped
public class ConversationScopedComponent implements Serializable {
        @Inject
        private Conversation conversation;
        private StringBuilder builder;
        
        public ConversationScopedComponent() {
                builder = new StringBuilder();
        }
        
        public void addMsg(String msg) {
                builder.append(msg).append(" : ");
        }
        
        public String getMsg() {
                return builder.toString();
        }
        
        public void conversationBegin() {
                if (conversation.isTransient()) {
                        conversation.begin();
                }
        }
        
        public void conversationEnd() {
                if (!conversation.isTransient()) {
                        conversation.end();
                }
        }

        @PostConstruct
        public void init() {
                System.out.println(">>>>> CDIStudy1ConversationScopedComponentインスタンス作成");
        }
        @PreDestroy
        public void preDestroy() {
                System.out.println("<<<<< CDIStudy1ConversationScopedComponentインスタンス破棄");
        }
        
        public StringBuilder getBuilder() {
                return builder;
        }

        public void setBuilder(StringBuilder builder) {
                this.builder = builder;
        }
}

次に、上記クラスをDIして使用するサービスクラスのソースです。このサービスクラスをManagedBeanではDIして使用するように、今回のサンプルを組みました。

FirstService.java
package alctail.service;

import javax.inject.Inject;

import alctail.cdi.ConversationScopedComponent;

/** 「@Dependent」がデフォルトのスコープ。使用される側のライフサイクルと同期する
   (参考:http://docs.oracle.com/javaee/6/tutorial/doc/gjbbk.html) */
public class FirstService {
        @Inject
        private ConversationScopedComponent component; 
        
        public void service() {
                component.conversationBegin();
                component.addMsg("だらしねぇという 戒めの心");
        }
}

上記サービスクラスを使っているManagedBeanのソースを以下に示します。

FirstManagedBean.java
package alctail.managedbean;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.inject.Inject;

import alctail.service.FirstService;


@RequestScoped
@ManagedBean
public class FirstManagedBean {
        
        @Inject
        private FirstService service; 
        
        public String moveNext() {
                service.service();
                return "success";
        }
        
        public String getScreenName() {
                return "最初の画面";
        }
}

こんな感じです。そして、今回は3画面作りましたので、上記のServiceクラスとManagedBeanが3セットあります。

2番目の画面用のServiceクラスとManagedBean

SecondService.java
package alctail.service;

import javax.inject.Inject;

import alctail.cdi.ConversationScopedComponent;


public class SecondService {
        @Inject
        private ConversationScopedComponent component; 
        
        public void service() {
                component.addMsg("歪みねぇという  賛美の心");
        }
}


SecondManagedBean.java
package alctail.managedbean;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.inject.Inject;

import alctail.service.SecondService;


@RequestScoped
@ManagedBean
public class SecondManagedBean {
        
        @Inject
        private SecondService service; 
        
        public String moveNext() {
                service.service();
                return "success";
        }
        
        public String getScreenName() {
                return "2番目の画面";
        }
}


3番目の画面(最後の画面)用のServiceクラスとManagedBean

ThirdService.java
package alctail.service;

import javax.inject.Inject;

import alctail.cdi.ConversationScopedComponent;


public class ThirdService {
        @Inject
        private ConversationScopedComponent component; 
        
        public void service() {
                component.addMsg("仕方ないという  許容の心");
                System.out.println("結果:"+component.getMsg());
                component.conversationEnd();
        }
}


ThirdManagedBean.java
package alctail.managedbean;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.inject.Inject;

import alctail.service.ThirdService;


@RequestScoped
@ManagedBean
public class ThirdManagedBean {
        
        @Inject
        private ThirdService service; 
        
        public String moveFirst() {
                service.service();
                return "success";
        }
        
        public String getScreenName() {
                return "3番目の画面";
        }
}

長ったらしく、だらだらとソースを書きましたが、見るべきは、FirstService.javaの中でcomponent.conversationBegin();としているところから、カンバセーションスコープの生存期間は開始し、ThirdService.javaでcomponent.conversationEnd();としたときに、生存期間が終了します。
なので、System.outで結果を出力していますが、最後の画面で「結果:だらしねぇという 戒めの心 : 歪みねぇという  賛美の心 : 仕方ないという  許容の心 : 」が出力されます。
ここで、生存期間が終了するので、文字列はクリアされ、最初の画面でゼロから文字列の積み上げが開始されます。なので、何度も画面を遷移しても、最後の画面で出力される文言は「結果:だらしねぇという 戒めの心 : 歪みねぇという  賛美の心 : 仕方ないという  許容の心 : 」になります。


DIするオブジェクトが、判別できない場合、アノテーションでどのインスタンスをDIするかを指定するサンプル(@Qualifierの使い方)

DIする対象フィールドが、abstractな型で、その実装クラスが複数ある場合、@Injectと書いただけでは、どれをDIすればいいのかわからずエラーとなります。
いくつか対応方法がありますが、一番柔軟性があると思われる(個人的主観)方法のサンプルです。結論から言うと、独自のアノテーションを作って、これを使え!とそのアノテーションを振ることで指定します。

以下のようなサーブレットを作成しました。DIしてほしいフィールド(ここではmessageCreator)に対し、@Normalというアノテーションをつけています。このアノテーションは自分で作成したものです。

CDIStudy3Servlet1.java
package alctail.servlet;

import java.io.PrintWriter;

import javax.inject.Inject;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import alctail.component.i.MessageCreator;
import alctail.component.qualifier.Easy;
import alctail.component.qualifier.Normal;

/**
 * DIするオブジェクトが、判別できない場合、アノテーションでどのインスタンスをDIするかを指定するサンプル
 */
@WebServlet(urlPatterns={"/Servlet1"})
public class CDIStudy3Servlet1 extends HttpServlet {
        
        // @Normal/@Easywを記述することで、どちらをDIするのか指定する
        @Inject @Normal
        private MessageCreator messageCreator;
        
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) {
                // テキストを返すようにする
                response.setContentType("text/html;charset=UTF-8");
                
                PrintWriter writer = null;
                try {
                        writer = response.getWriter();
                        writer.println("<html><body>");
                        writer.println("<p>"+messageCreator.createMsg()+"</p>");
                        writer.println("</body></html>");
                } catch(Exception e) {
                        e.printStackTrace();
                }
        }

}


MessageCreator.java
package alctail.component.i;

public interface MessageCreator {
        public String createMsg();
}


EasyMessageCreator.java
package alctail.component;

import alctail.component.i.MessageCreator;
import alctail.component.qualifier.Easy;

@Easy
public class EasyMessageCreator implements MessageCreator {
        
        public String createMsg() {
                return "イージーモードです";
        }
}


NormalMessageCreator.java
package alctail.component;

import alctail.component.i.MessageCreator;
import alctail.component.qualifier.Normal;

@Normal
public class NormalMessageCreator implements MessageCreator {
        
        public String createMsg() {
                return "ノーマルモードです。";
        }
}


Easy.java
package alctail.component.qualifier;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.inject.Qualifier;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.PARAMETER;


@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface Easy {

}


Normal.java
package alctail.component.qualifier;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.inject.Qualifier;


@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface Normal {

}

サンプルを見るとわかりますが、独自のアノテーションを作成し、そこに「@Qualifier」をつけることで、DIの際の識別子として使用することを示します。
そして、そのアノテーションをDI対象のクラスにも付与します。
そして、最後にDIするフィールドのところにも、そのアノテーションを付与します。これで、DIコンテナはどれをDIすべきか判断できるようになり、正常に処理が実行されます。


@Producesの使い方

インスタンスを生成するFactoryクラスのような使い方が出来ます。
使い方の例を以下に示します。

SampleServiceクラス内のアノテーションのつけ方と、ComponentFactoryクラスの実装が今回のポイントです。@Producesを使えば、DIするインスタンスの作成は通常デフォルトコンストラクタを呼び出し生成されますが、特定のメソッドを通してインスタンスを生成することが出来ます。

CDIStudy4Servlet1.java
package alctail.servlet;

import java.io.PrintWriter;

import javax.inject.Inject;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import alctail.service.SampleService;


/**
 * 「@Produces」で作成する場合は、アノテーションをつけて、どのProducesメソッドを実行するのかの情報を与えてやらねばダメみたい。
 * 今回のサンプルでは、SpecifyComponentを作成して使用した。1個作ればほかのクラスを作成するProducesメソッドにも使える様子。
 * 今回のサンプルで、SpecifyComponentを使っている箇所に「@Named(name="〜〜〜")」でも動くものは作れる。
 * だが、nameに指定する文字列で、もめそうなのでアノテーションが良いかと。
 */
@WebServlet(urlPatterns={"/Servlet1"})
public class CDIStudy4Servlet1 extends HttpServlet {
        
        @Inject
        private SampleService service;
        
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) {
                // テキストを返すようにする
                response.setContentType("text/html;charset=UTF-8");
                
                PrintWriter writer = null;
                try {
                        writer = response.getWriter();
                        writer.println("<html><body>");
                        writer.println("<p>"+service.service()+"</p>");
                        writer.println("</body></html>");
                } catch(Exception e) {
                        e.printStackTrace();
                }
        }

}


SampleService.java
package alctail.service;

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

import alctail.component.SampleComponent;
import alctail.component.SampleComponentChild;
import alctail.component.SampleComponentOther;
import alctail.component.qualifier.SpecifyComponent;
import alctail.component.qualifier.SpecifyComponentChild;

@RequestScoped
public class SampleService {
        
        @Inject @SpecifyComponent
        private SampleComponent component;
        
        @Inject @SpecifyComponentChild
        private SampleComponentChild componentChild;
        
        @Inject @SpecifyComponent
        private SampleComponentOther componentOther;
        
        public String service() {
                return component.getMsg()+" : "+componentChild.getMsg()+" : "+componentOther.getMsg();
        }

}


このクラスで、3つインスタンスを作成するメソッドを定義してありますが、たとえば、この中の1つのメソッドがメソッド名だけ変えて増えると、コンパイルは通りますが、デプロイで失敗します(どのメソッドを呼べばよいのか判断できなくなってしまうので)。

ComponentFactory.java
package alctail.factory;

import javax.enterprise.inject.Produces;

import alctail.component.SampleComponent;
import alctail.component.SampleComponentChild;
import alctail.component.SampleComponentOther;
import alctail.component.qualifier.SpecifyComponent;
import alctail.component.qualifier.SpecifyComponentChild;

public class ComponentFactory {
        
        @Produces @SpecifyComponent
        public SampleComponent createSampleComponent() {
                SampleComponent result = new SampleComponent();
                result.setMsg("ComponentFactory#createSampleComponentで作ったんだよ!");
                return result;
        }
        
        @Produces @SpecifyComponentChild
        public SampleComponentChild createSampleComponentChild() {
                SampleComponentChild result = new SampleComponentChild();
                result.setMsg("ComponentFactory#createSampleComponentChildで作ったんだよ!");
                return result;
        }
        
        @Produces @SpecifyComponent
        public SampleComponentOther createSampleComponent2() {
                SampleComponentOther result = new SampleComponentOther();
                result.setMsg("ComponentFactory#createSampleComponentOtherで作ったんだよ!");
                return result;
        }

}


SampleComponent.java
package alctail.component;

public class SampleComponent {
        
        private String msg;

        public String getMsg() {
                return msg;
        }

        public void setMsg(String msg) {
                this.msg = msg;
        }
}


SampleComponentChild.java
package alctail.component;

public class SampleComponentChild extends SampleComponent {     
}


SampleComponentOther.java
package alctail.component;

public class SampleComponentOther {
        
        private String msg;

        public String getMsg() {
                return msg;
        }

        public void setMsg(String msg) {
                this.msg = msg;
        }
}


SpecifyComponent.java
package alctail.component.qualifier;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.inject.Qualifier;

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface SpecifyComponent {

}


SpecifyComponentChild.java
package alctail.component.qualifier;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.inject.Qualifier;

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface SpecifyComponentChild {
}


インターセプターの使い方

EJBにもインターセプターはありましたが、これは別物なので注意。

以下の例では、SampleServiceクラスのserviceメソッドを呼び出すタイミングでインターセプターが走ります(@SampleInterceptorAnnotationが付与されたメソッドはすべてインターセプターが走る)。
また、beans.xmlファイルに、インターセプターのクラスを登録しておく必要があります。beans.xmlファイルはWEB-INFディレクトリ直下に配置する必要があります。

CDIStudy5Servlet1.java
package alctail.servlet;

import java.io.PrintWriter;

import javax.inject.Inject;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import alctail.interceptor.i.SampleInterceptorAnnotation;
import alctail.service.SampleService;

/**
 * インターセプターのサンプル
 */
@WebServlet(urlPatterns={"/Servlet1"})
public class CDIStudy5Servlet1 extends HttpServlet {
        
        @Inject
        private SampleService service;
        
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) {
                // テキストを返すようにする
                response.setContentType("text/html;charset=UTF-8");
                
                PrintWriter writer = null;
                try {
                        writer = response.getWriter();
                        writer.println("<html><body>");
                        writer.println("<p>"+service.service()+"</p>");
                        writer.println("</body></html>");
                } catch(Exception e) {
                        e.printStackTrace();
                }
        }

}


SampleService.java
package alctail.service;

import alctail.interceptor.i.SampleInterceptorAnnotation;

/**
 * 「@SampleInterceptorAnnotation」はクラスにつければ、メソッド全体
 * メソッドだけにつければ、そのメソッドが実行された場合のみとなります。
 */
public class SampleService {
        
        @SampleInterceptorAnnotation
        public String service() {
                try {
                        Thread.sleep(1000);
                } catch (Exception e){}
                return "ゆがみねぇな";
        }
}


SampleInterceptor.java
package alctail.interceptor;

import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;

import alctail.interceptor.i.SampleInterceptorAnnotation;

/**
 * Interceptorとして動作させるためには、beans.xmlにクラスを追加する必要有り
 */
@SampleInterceptorAnnotation
@Interceptor
public class SampleInterceptor {
        
        @AroundInvoke
        public Object yokohairi(InvocationContext ic) throws Exception {
                try {
                        long startTime = System.nanoTime();
                        Object result = ic.proceed();
                        long finishTime = System.nanoTime();
                        System.out.println("処理にかかった時間 : "+(finishTime-startTime));
                        return result;
                } catch (Exception e) {
                        System.out.println("例外が発生。仕方ないね。");
                        throw e;
                }
        }
}


SampleInterceptorAnnotation.java
package alctail.interceptor.i;

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 SampleInterceptorAnnotation {
}


beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- This file can be an empty text file (0 bytes) We're declaring the schema 
        to save you time if you do have to configure this in the future -->
<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>alctail.interceptor.SampleInterceptor</class>
        </interceptors>
</beans>


Decoratorの使い方

@Decoratorを使うことで、特定のメソッドが呼び出された場合、事前に指定されたメソッドに制御を移すことが出来ます。癖のある動きをしますね。。。
以下にサンプルを示します。

@Decoratorをつけたクラスは、beans.xmlに登録する必要があります。beans.xmlファイルはWEB-INFディレクトリ直下に置く必要があります。

CDIStudy6Servlet1.java
package alctail.servlet;

import java.io.PrintWriter;

import javax.inject.Inject;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import alctail.service.i.SampleServiceInterface;

/**
 * Decoratorのサンプル。
 * Decoratorを定義すると、インスタンスの生成にDecoratorのメソッドが呼ばれるようになる。
 * 「@Decorator」と「@Delegate」はセットで使う。
 *
 */
@WebServlet(urlPatterns={"/Servlet1"})
public class CDIStudy6Servlet1 extends HttpServlet {
        @Inject
        private SampleServiceInterface service;
        
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) {
                // テキストを返すようにする
                response.setContentType("text/html;charset=UTF-8");
                
                PrintWriter writer = null;
                try {
                        writer = response.getWriter();
                        writer.println("<html><body>");
                        writer.println("<p>"+service.service("☆☆☆")+"</p>");
                        writer.println("</body></html>");
                } catch(Exception e) {
                        e.printStackTrace();
                }
        }
}


SampleService.java
package alctail.service;

import alctail.service.i.SampleServiceInterface;

public class SampleService implements SampleServiceInterface {
        
        public String service(String msg) {
                System.out.println("SampleService#service called");
                return msg+"歪みねぇな";
        }
}


SampleServiceInterface.java
package alctail.service.i;

public interface SampleServiceInterface {
        
        public String service(String msg);

}


SampleServiceDecoratora.java
package alctail.service.decorator;

import javax.decorator.Decorator;
import javax.decorator.Delegate;
import javax.inject.Inject;

import alctail.service.i.SampleServiceInterface;

@Decorator
public class SampleServiceDecorator implements SampleServiceInterface {
        
        @Inject
        @Delegate
        private SampleServiceInterface service;
        
        public String service(String msg) {
                System.out.println("SampleServiceDecorator#service called");
                return msg+service.service("涼ちんk");
        }
}


beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- This file can be an empty text file (0 bytes) We're declaring the schema 
        to save you time if you do have to configure this in the future -->
<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">
        <decorators>
                <class>alctail.service.decorator.SampleServiceDecorator</class>
        </decorators>
</beans>

注目すべき点は、@Decoratorをつけたクラス(SampleServiceDecorator)と、SampleService.javaはソース上、上位クラスが同じだというだけで、関連がゼロです。
にも関わらず、出力結果は「☆☆☆涼ちんk歪みねぇな」となり、SampleServiceDecoratorのメソッドが呼ばれていることがわかります。

@Decoratorを使うと、呼び出す側が想定しているメソッドと、実際に実行するメソッドが変わります(今回のサンプルでは、SampleserviceDecorator#serviceクラスが結果として呼び出されている)。
ゆがみねぇな(いやむしろ、ゆがみばっかりなのか?w)。


「@Alternative」の使い方について

@Alternativeの使いどころは、環境によってDIするクラスを変更したい場合・・・などです。
DIするクラスに対し、@Alternativeアノテーションを付与し、beans.xmlでどれを使用するかクラス名を記述すると、そのクラスがDIされるようになります。

CDIStudy7Servlet1.java
package alctail.servlet;

import java.io.PrintWriter;

import javax.decorator.Decorator;
import javax.decorator.Delegate;
import javax.inject.Inject;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import alctail.service.i.SampleServiceInterface;

/**
 * 「@Alternative」の使い方について。
 * 「@Alternative」を使うと、、異なる配備環境向けにアプリケーションを構成できる
 * (beans.xmlでの記述次第で、何をDIするか指定できる)
 *
 */
@WebServlet(urlPatterns={"/Servlet1"})
public class CDIStudy7Servlet1 extends HttpServlet {
        @Inject
        private SampleServiceInterface service;
        
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) {
                // テキストを返すようにする
                response.setContentType("text/html;charset=UTF-8");
                
                PrintWriter writer = null;
                try {
                        writer = response.getWriter();
                        writer.println("<html><body>");
                        writer.println("<p>"+service.service()+"</p>");
                        writer.println("</body></html>");
                } catch(Exception e) {
                        e.printStackTrace();
                }
        }
}


SampleServiceInterface.java
package alctail.service.i;

public interface SampleServiceInterface {
        public String service();
}


SampleServiceA.java
package alctail.service;

import javax.enterprise.inject.Alternative;

import alctail.service.i.SampleServiceInterface;

@Alternative
public class SampleServiceA implements SampleServiceInterface {
        
        public String service() {
                return "SampleServiceAだよ!";
        }
}


SampleServiceB.java
package alctail.service;

import javax.enterprise.inject.Alternative;

import alctail.service.i.SampleServiceInterface;

@Alternative
public class SampleServiceB implements SampleServiceInterface {
        
        public String service() {
                return "SampleServiceBだよ!";
        }
}


beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- This file can be an empty text file (0 bytes) We're declaring the schema 
        to save you time if you do have to configure this in the future -->
<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">
        <alternatives>
                <class>alctail.service.SampleServiceB</class>
        </alternatives>
</beans>

beans.xml内で、どれをDIするか指定します。上記の場合、DIされるクラスはSampleServiceBとなります。SampleServiceAをDIしたい場合、beans.xmlをSampleServiceBからSampleServiceAに書き換えれば変更可能です。