DeltaSpikeのBeanManagerProviderとCDIのBeanManagerの使いどころ(個人的見解)
DeltaSpikeにはBeanManagerProviderなるクラスが存在します。これはいわゆるCDIのBeanManagerクラスを取得するためのアクセッサクラスです。
JSR-299ではBeanManagerを取得する場合、@InjectによるDIかJNDIルックアップによる取得のみのようですが、DeltaSpikeを利用すれば、第3の手段として、Providerから取得することができます。
BeanManager beanManager = BeanManagerProvider.getInstance().getBeanManager();
ServletListenerで起動、終了処理時にCDIの管理Beanを直接操作したい場合にはこのProviderから簡単に取得できるので便利です。
直接BeanManagerを触る機会は少ないように思えますが、例えば、ベンダー固有の特殊なライブラリや、レガシーシステムと接続するようなライブラリを使用する必要がある場合、それらCDI管理Beanではないため@Injectでインジェクションすることができません。
しかしながら、BeanManagerを利用すれば、特殊なライブラリとCDI管理Beanをうまく統合できるかもしれません。(利用するライブラリのインターフェースにもよるので一概にはいえないのですが。)
例えば、レガシーシステムと接続するためのLegacyConnectionFactoryクラスがあり、このクラスはLegacyConnectionを生成するとします。LegacyConnectionクラスにはconnect、disconnect、getSenderの3つのメソッドがあるとします。connect/disconnectは接続および切断を行い、getSenderメソッドはSenderインターフェースを実装したクラスを生成し、メッセージ送信者はこのSenderインターフェースのsendメソッドを用いてメッセージをレガシーシステムに送信するような場合を考えてください。(構成が若干JMSに似ていますが気にせず進めます。)
いずれのクラスも別のjarとして提供されておりCDI管理対象外のクラスであったときに、メッセージを送信するためにはSenderを取得しなければなりませんが、SenderはLegacyConnectionからしか取得できません。コネクションは一度connectすればいいものとします。
このような場合、個人的にはServletListenerでコネクションの取得とconnect、disconnectを行い、CDI管理BeanのプロデューサクラスにBeanManagerからアクセスし、コネクションを設定。プロデューサクラスは設定されたコネクションから要求に応じてメッセージ送信者にSenderオブジェクトを渡すというのが1つの解決策のように思えます。
まずサーブレットリスナを準備。
@WebListener public class BootstrappingListener implements ServletContextListener { private BeanManager beanManager; private LegacyConnection connection; @Override public void contextDestroyed(ServletContextEvent arg0) { connection.disconnect(); } @SuppressWarnings("unchecked") @Override public void contextInitialized(ServletContextEvent arg0) { // Legacyシステムと接続するためのコネクション取得 connection = LegacyConnectionFactory.getConnection(); // 接続 connection.connect(); // BeanManagerProviderよりBeanMangerを取得 beanManager = BeanManagerProvider.getInstance().getBeanManager(); Bean<LegacySenderProducer> bean = (Bean<LegacySenderProducer>) beanManager.resolve( beanManager.getBeans(LegacySenderProducer.class)); CreationalContext<LegacySenderProducer> context = beanManager.createCreationalContext(bean); // BeanManagerからLegacySenderProducerを取得 LegacySenderProducer producer = (LegacySenderProducer) beanManager.getReference(bean, LegacySenderProducer.class, context); // connectionを設定 producer.setConnection(connection); } }
LegacySenderProducerがメッセージ送信者に対してSenderオブジェクトを提供するプロデューサです。
【LegacySenderProducer】
@Named @Singleton public class LegacySenderProducer { private LegacyConnection connection; @Produces @LegacySender public Sender getSender() { return connection.getSender(); } public LegacyConnection getConnection() { return connection; } public void setConnection(LegacyConnection connection) { this.connection = connection; } }
getSenderメッソッドに@Producesと@LegacySenderというアノテーションが付与されていますが、@LegacySenderはCDIのQualifierアノテーションを利用したものであり、このクラスを簡単にざっくり説明すると、要はメッセージ送信BeanのSenderプロパティに@Injectと@LegacySenderという2つのアノテーションがあればBean生成時にこのプロデューサクラスのgetSenderのからSenderがDIされるということです。
@LegacySenderはこんな感じです。
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import javax.inject.Qualifier; @Qualifier @Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface LegacySender { }
さらに、メッセージ送信用のBeanクラスはこんな感じです。
@Named @RequestScoped public class LegacyMessageSender { @Inject @LegacySender private Sender sender; private String message; public String sendMessage() { sender.send(message); return "confirmMessage.xhtml"; } ... }
ついでにメッセージ送信用のViewと、確認用のViewはこんな感じ
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets"> <h:head> <title>Message input.</title> </h:head> <h:body> <h:outputText value="Input your message." style="font-style: italic; fontsize: 1.5em" /> <br /> <h:form> <h:panelGrid columns="2"> message: <h:inputText value="#{legacyMessageSender.message}" /> </h:panelGrid> <h:commandButton value="Submit message" action="#{legacyMessageSender.sendMessage}" /> </h:form> </h:body> </html>
【confirmMessage.xhtml(確認用)】
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets"> <h:head> <title>Message confirmation.</title> </h:head> <h:body> Your message is successfully sent. [ #{legacyMessageSender.message} ] <h:form> <h:commandLink value="Back" action="./inputMessage.xhtml" /> </h:form> </h:body> </html>
あくまでも説明用のサンプルなのでかなり強引なものになってしまいましたが、実際はこんな簡単にハマるケースはないと思います。
今回の趣旨は、とりあえず、BeanManagerProviderからCDIのBeanManagerにアクセスできるということと、BeanManagerを利用すればCDIの管理Beanにアクセスできるということの説明がメインなので、レガシーなんとかとかは適当に読み飛ばしてください。