インジェクションするオブジェクトの切り替え

スレテオタイプアノテーションを使い、
インジェクションするオブジェクトの切り替えを行います。

【前提条件】

[環境]

【概要】

切り替え対象となるオブジェクトにつけるアノテーションの作成と
作成したアノテーションを適用するための設定変更を行います。

【サンプルコード】

[前提となるソース]

ベースとなるソースです。
[ページ]

<?xml version="1.0" encoding="UTF-8" ?>
<!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:f="http://java.sun.com/jsf/core"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:h="http://java.sun.com/jsf/html">
    <f:view>
        <h:form id="searchForm">
            <h:dataTable value="#{userListPage.getUserList()}" var="user">
                <h:column>
                    <f:facet name="header">
                        ユーザID
                    </f:facet>
                    <h:outputText value="#{user.userId}" />
                </h:column>
                <h:column>
                    <f:facet name="header">
                        ユーザ名
                    </f:facet>
                    <h:outputText value="#{user.name}" />
                </h:column>
            </h:dataTable>
        </h:form>
    </f:view>
</html>

[ManagedBean]

package jp.glory.ui.impl;

import java.util.List;

import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;

import jp.glory.application.UserApplication;
import jp.glory.infrastructure.persistence.entity.UserData;

@Named(value = "userListPage")
@RequestScoped
public class UserListPage {

    @Inject
    private UserApplication applicaiton = null;

    /**
     * @return userList
     */
    public List<UserData> getUserList() {
        return applicaiton.findAllList();
    }

}

[アプリケーションインターフェイス]

package jp.glory.application;

import java.util.List;

import jp.glory.infrastructure.persistence.entity.UserData;

public interface UserApplication {

    List<UserData> findAllList();
}

[アプリケーションクラス]

package jp.glory.application.impl;

import java.util.List;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;

import jp.glory.application.UserApplication;
import jp.glory.infrastructure.persistence.entity.UserData;

@Stateless
public class UserApplicationImpl implements UserApplication {

    @PersistenceContext(unitName = "blog")
    private EntityManager manager = null;

    @Override
    public List<UserData> findAllList() {

        final TypedQuery<UserData> query = manager.createNamedQuery("findAll", UserData.class);

        return query.getResultList();
    }
}

[Entity]

package jp.glory.infrastructure.persistence.entity;

import java.io.Serializable;
import java.sql.Timestamp;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinColumns;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@Entity
@Table(name = "userdata")
@NamedQueries({
        @NamedQuery(name = "findAll", query = "SELECT u FROM UserData u"),
        @NamedQuery(name = "findByUserId", query = "SELECT u FROM UserData u WHERE u.userId = :userId"),
})
public class UserData implements Serializable {

    private static final long serialVersionUID = 6528642302239465812L;

    @Id
    @Column(name = "user_id")
    private String userId = null;

    private String name = null;

    @Column(name = "last_update")
    private Timestamp lastUpdate = null;

    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumns({
        @JoinColumn(name = "auth_user_id", referencedColumnName = "user_id")
    })
    private List<UserAuthority> authorityList = null;

    // アクセサメソッドは省略
}
<?xml version="1.0" encoding="UTF-8"?>
<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://docs.jboss.org/cdi/beans_1_0.xsd">
</beans>
[ステレオタイプアノテーション]

切り替え対象となる明示するための
アノテーションを作成します。

package jp.glory.common.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.enterprise.inject.Alternative;
import javax.enterprise.inject.Stereotype;

@Alternative
@Stereotype
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SampleMock {

}

javax.enterprise.inject.Alternativeアノテーションを使用します。

Alternativeアノテーションをつけたクラスは
CDIのインジェクション対象から外れます。
インジェクション対称にする場合はbeans.xmlに設定を追記します。

ステレオタイプアノテーションなので、
javax.enterprise.inject.Stereotypeアノテーションをつけます。

TargetアノテーションにはTYPEを指定します。

RetentionアノテーションにはRUNTIMEを指定します。

[切り替え先のクラス]

サンプルではUserApplicationインターフェイス
サブクラスのスタブクラスを作成します。

package jp.glory.test.application.stub;

import java.util.ArrayList;
import java.util.List;

import jp.glory.application.UserApplication;
import jp.glory.common.annotation.SampleMock;
import jp.glory.infrastructure.persistence.entity.UserData;

@SampleMock
public class UserApplicationStub implements UserApplication {

    @Override
    public List<UserData> findAllList() {

        final List<UserData> dataList = new ArrayList<>();

        dataList.add(createStubData("STUB0001", "NAME0001"));
        dataList.add(createStubData("STUB0002", "NAME0002"));
        dataList.add(createStubData("STUB0003", "NAME0003"));
        dataList.add(createStubData("STUB0004", "NAME0004"));
        dataList.add(createStubData("STUB0005", "NAME0005"));

        return dataList;
    }

    private UserData createStubData(final String userId, final String name) {

        final UserData returnData = new UserData();

        returnData.setUserId(userId);
        returnData.setName(name);

        return returnData;
    }
}

作成したSampleMockアノテーションをつけています。
それ以外は画面に表示するためのデータを作成する処理を行っているだけです。

[beans.xml]

Alternativeの設定を有効にするためには
beans.xmlを書き換えます。

<?xml version="1.0" encoding="UTF-8"?>
<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://docs.jboss.org/cdi/beans_1_0.xsd">
    <alternatives>
        <stereotype>jp.glory.common.annotation.SampleMock</stereotype>
    </alternatives>
</beans>

alternativesタグで指定したものが有効になります。
サンプルでは作成したアノテーションをstereotypeタグで定義しています。

[実行1]

まずはbeans.xmlの設定で
alternativesタグの定義を行わなかった場合の画面です。

DBの値が出力されています。

[実行2]

続いてbeans.xmlの設定で
alternativesタグの定義を行った場合の画面です。

スタブクラスで作成したデータが出力されています。

[最後に]

実際の開発では連携先のシステムがある場合、
ローカルではスタブクラスを使い、テスト/本番環境では本物のクラスを使う
というニーズは良くあると思います。
(DDDで言うところの腐敗防止層です)

Alternativeアノテーションを使えば、
容易に切り替えができます。

beans.xml内部に直接書かずに
環境ごとに変更する箇所だけ別のXMLファイルに切り出したいのですが、
そういった機能はないようです。