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」の使い方について
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(); } } } |
このカンバセーションスコープは、画面と一緒につかわないと動かないみたいです(やってみた感じ、そんなんだった)。なので、今回は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する対象フィールドが、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すべきか判断できるようになり、正常に処理が実行されます。
インスタンスを生成する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をつけたクラスは、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の使いどころは、環境によって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に書き換えれば変更可能です。