Help us understand the problem. What is going on with this article?

CDIをJava SE環境で動かす - Weld SE

More than 3 years have passed since last update.

概要

本来はJava EE環境(Java EE サーバ上)で動くCDIを、Java SE環境で動かしてみます。
CDIの参照実装であるWeldのSE版を使います。

今回作成したサンプルコードは、GitHubにアップしてあります。
https://github.com/sumeragizzz/weldse-examples

環境

Windows 10 Pro 64bit
Java SE 8 Update 102 64bit
Weld SE 2.4.0.Final

準備

Maven

ビルドツールにMavenを使用します。
pom.xmlに以下の依存情報を追加します。

pom.xml
<dependency>
    <groupId>org.jboss.weld.se</groupId>
    <artifactId>weld-se</artifactId>
    <version>2.4.0.Final</version>
</dependency>

Mavenリポジトリにはweld-se-coreというのもありますが、こちらは自分で依存関係を解決する必要があるので、通常はweld-seでいいと思います。

beans.xml

Weld SEでCDIを有効にするにはbeans.xmlが必要なようです。
Java EE 7では無くても有効になっているので、ちょっと違いますね。
実際にbeans.xml無しで実行してみたところ、以下の例外が発生しました。

java.lang.IllegalStateException: WELD-ENV-000016: Missing beans.xml file in META-INF

なので、以下にbeans.xmlを配置します。

/[project-root]/src/main/resources/META-INF/beans.xml

ファイルの存在有無しかみていないらしく、0byteファイルでも有効化されました。
ですが、本来は妥当なXMLにしておくべきだと思います。

実装

インスタンス取得

まずはシンプルにコンテナからCDI Beanのインスタンスを取得してみます。

サンプルとして、文字列を返すだけのBeanを用意しました。

HelloWorldBean.java
public class HelloWorldBean {
    public String execute() {
        return "Hello World";
    }
}

WeldからWeldContainerを生成して、クラス指定で取得できます。
WeldContainerはAutoClosableを実装するので、try-with-resourcesが使えます。

main
Weld weld = new Weld();
try (WeldContainer container = weld.initialize()) {
    HelloWorldBean bean = container.select(HelloWorldBean.class).get();
    System.out.println(bean.execute());
}

コンテナがCDI Beanを生成、管理しているのが分かります。

実行結果
Hello World

DI

CDI Beanに別のCDI BeanをInjectしてみます。

フィールドインジェクション

一番手っ取り早いフィールドインジェクションから。

FieldInjectionBean.java
@Inject
private HelloWorldBean helloWorldBean;

public String execute() {
    return String.format("*** %s ***", helloWorldBean.execute());
}

取得するCDI Beanのみ変えて、先ほどと同じように実行します。

FieldInjectionMain.java
Weld weld = new Weld();
try (WeldContainer container = weld.initialize()) {
    FieldInjectionBean bean = container.select(FieldInjectionBean.class).get();
    System.out.println(bean.execute());
}

DIしたCDI Beanが返す文字列が含まれているので、DIできていることが分かります。

実行結果
*** Hello World ***

コンストラクタインジェクション

次はコンストラクタ経由でDIします。
イミュータブルなクラス設計にできたりするメリットがありますね。

ConstructorInjectionBean.java
private HelloWorldBean helloWorldBean;
@Inject
public ConstructorInjectionBean(HelloWorldBean helloWorldBean) {
    this.helloWorldBean = helloWorldBean;
}
public String execute() {
    return String.format("\"%s\"", helloWorldBean.execute());
}

実行方法は先ほどと同様。
期待通りにDIできています。

実行結果
"Hello World"

セッターインジェクション

あまり存在意義を感じないので割愛します。

複数コンテナ

複数のコンテナを生成して使い分けることができます。

下記サンプルでは、IDを振り、自動検出を無効化、指定したクラスが属するパッケージを管理対象として、コンテナを生成しています。
Weld#shutdown()を呼ぶと、そのWeldインスタンスから生成した全てのWeldContainerをclose()してくれます。
AutoClosableではないので、finallyで呼び出すようにしました。

MultiContainerMain.java
Weld weld = new Weld();
try {
    WeldContainer container1 = weld.containerId("1").disableDiscovery().packages(HelloWorldBean.class).initialize();
    WeldContainer container2 = weld.containerId("2").disableDiscovery().packages(OtherPackageBean.class).initialize();

    System.out.println(container1.select(HelloWorldBean.class).get().execute());
    System.out.println(container1.select(ConstructorInjectionBean.class).get().execute());
    System.out.println(container2.select(OtherPackageBean.class).get().execute());

    // UnsatisfiedResolutionException発生
    // System.out.println(container1.select(OtherPackageBean.class).get().execute());
    // System.out.println(container2.select(HelloWorldBean.class).get().execute());
} finally {
    weld.shutdown();
}
実行結果
Hello World
"Hello World"
Other Package

管理対象ではないCDI Beanを取得しようとすると、実行時にUnsatisfiedResolutionExceptionが発生します。
コメントアウトしているコードで例外が発生するので、コンテナごとに管理対象が異なることが分かります。

インターセプター

まず、インターセプターを実装します。
実行時間を計測して、標準出力するようにしてみました。

ElapsedTimeInterceptor.java
@Interceptor
@ElapsedTimeBinding
public class ElapsedTimeInterceptor {
    @AroundInvoke
    public Object invoke(InvocationContext context) throws Exception {
        long start = System.nanoTime();
        Object result = context.proceed();
        long end = System.nanoTime();
        System.out.format("%,dns\r\n", end - start);
        return result;
    }
}

CDI Beanにインターセプターを適用する為の@InterceptorBindingなアノテーションを作ります。

ElapsedTimeBinding.java
@Inherited
@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface ElapsedTimeBinding {
}

これをCDI Beanに付与します。
一瞬で終わってしまうと計測時間の結果がイマイチなので、1秒ほどスリープさせてみました。

InterceptedBean.java
@ElapsedTimeBinding
public class InterceptedBean {
    public String execute() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return getClass().getName();
    }
}

これで実行しましたが、インターセプターが動きません・・・
beans.xmlにインターセプターを定義しておく必要がある、という情報が参考サイトにありました。
0byteファイルにしてましたが、ちゃんと定義するようにしました。

beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans
    xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://xmlns.jcp.org/xml/ns/javaee
        http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
    bean-discovery-mode="all">
    <interceptors>
        <class>examples.weldse.interceptor.ElapsedTimeInterceptor</class>
    </interceptors>
</beans>

これで実行したところ、期待通りにインターセプターが動作しました。
なにげにCDI BeanのインスタンスがProxyになっているのが見えますね。

実行方法
1,000,119,111ns
examples.weldse.bean.InterceptedBean$Proxy$_$$_WeldSubclass

@InterceptorBindingを使う場合は上記のようにbeans.xmlに定義が必要なようです。
@Interceptorsを使えば、beans.xmlの定義有無は関係なく動作したので、気軽に使うのであればこちらも選択肢ですね。

まとめ

これくらい手軽なら、ちょっとしたコンソールアプリケーションを作る際に採用してもいいと思います。
個人的には、インターセプターを使えるのが嬉しいです。
@Transactionalを使いたいところですが、JTAが無いので、このままではダメですね。
これがどうにかなるのかは、今後の課題としておきます。

参考

http://docs.jboss.org/weld/reference/latest/en-US/html_single/
http://d.hatena.ne.jp/Kazuhira/20140208/1391851068
http://qiita.com/opengl-8080/items/2f03ab496e871cf32f54
http://qiita.com/opengl-8080/items/431de9175dca33a09ba8
http://enterprisegeeks.hatenablog.com/entry/2015/12/07/075311

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした