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はこんな感じ

【inputMessage.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 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にアクセスできるということの説明がメインなので、レガシーなんとかとかは適当に読み飛ばしてください。