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


# 概要
本来は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に以下の依存情報を追加します。

```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を用意しました。

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

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

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

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

```java:実行結果
Hello World
```

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

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

```java:FieldInjectionBean.java
@Inject
private HelloWorldBean helloWorldBean;

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

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

```java: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できていることが分かります。

```java:実行結果
*** Hello World ***
```

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

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

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

```java:実行結果
"Hello World"
```

### セッターインジェクション
あまり存在意義を感じないので割愛します。

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

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

```java: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();
}
```

```java:実行結果
Hello World
"Hello World"
Other Package
```

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

## インターセプター


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

```java: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なアノテーションを作ります。

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

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

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

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

```xml: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になっているのが見えますね。

```java:実行方法
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