CDI

Contexts and Dependency Injection

Java EE6 /WEB-INF/beans.xmlが存在する場合にCDIが有効になる(内容は空でも良い)、スコープアノテーションは暗黙的に@Dependentとみなされる

Java EE7 beans.xmlなしで有効されるが、スコープアノテーション(@Dependentも含む)指定が必須

GlassFish 4.0 CDI 1.1 (Java EE7)

JBoss AS 7 CDI 1.0 (Java EE6)

JavaEEのCDI設計図

★概要

Injectionされる側の条件(コンテナ管理Beanであること)

・Servlet関連 @WebServlet @WebFilter @WebListener

・EJB Session Bean @Statelessなど

・JAX-RS @Pathなど

・CDIによって生成されたインスタンス

CDIに生成される側の条件

・引数なしコンストラクタを含む

・インタフェースの実装はなくてもインジェクション可能

・アノテーションなしの完全なPOJO

※他サイト参照

★beans.xml構成

CDI 1.0

<interceptors>

<class>

<decorators>

<class>

<alternatives>

<class>

<stereotype>

CDI 1.1

<beans ...

bean-discovery-mode="all">

...

</beans>

all 全てのクラスがインジェクション可能 (CDI 1.0に相当)

@Ventedが付与されたクラスは、インジェクションの対象を外す

annotated スコープアノテーションが付与されたクラス・セッションBeanがインジェクション可能

none インジェクションしない

★JavaEE6のDIサンプルコード

import javax.annotation.Resource;

import javax.ejb.EJB;

import javax.ejb.EJBContext;

import javax.ejb.SessionContext;

import javax.ejb.TimerService;

import javax.inject.Inject;

import javax.jms.ConnectionFactory;

import javax.jms.Queue;

import javax.jms.Topic;

import javax.mail.Session;

import javax.persistence.EntityManager;

import javax.persistence.EntityManagerFactory;

import javax.persistence.PersistenceContext;

import javax.persistence.PersistenceUnit;

import javax.sql.DataSource;

import javax.transaction.UserTransaction;

import javax.xml.ws.WebServiceRef;

@Stateless

public class MyEJB {

// local/remoteインターフェース

@EJB

private MyEJB myEJB;

@WebServiceRef

private MyWebService myWebService;

@WebService(name="service", serviceName="MyService", targetNamespace="http://xxx.com")

private MyWebService myWebService;

@Resource

private SessionContext ctx;

// @Namedされたクラス

@Inject

private MyRepository myRepository;

// ejb-jar.xmlで指定

@Resource(name = "currencyEntry")

private String currency;

// JMS

@Resource

private ConnectionFactory factory;

@Resource

private Queue queue;

@Resource

private Topic topic;

// JavaMail

@Resource

private Session session;

// JTA

@Resource

private UserTransaction ut;

@Resource

private EJBContext context;

@Resource

private TimerService service;

@Resource(name="jdbc/xxx")

private DataSource ds;

// JPA

@PersistenceContext(unitName="testU") // 方法1

private EntityManager em;

@PersistenceUnit(unitName="testU")

private EntityManagerFactory emf;

private EntityManager em;

@PostConstruct

public void initialize() {

em = emf.createEntityManager(); // 方法2

}

}

@Stateful

@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)

public class MyEJB {

@PersistenceContext(unitName = "testU", type = PersistenceContextType.EXTENDED) // EXTENDEDはStatefulのみ

private EntityManager em;

...

}

public class OrderBean {

@Inject

@MyQualifier

private OrderDao dao; // Field


@Inject

public OrderBean(@MyQualifier OrderDao dao) { } // Constructor


@Inject

public void init(@MyQualifier OrderDao dao) { } // Initializer


@Inject

public void setOrderDao(@MyQualifier OrderDao dao) { } // Setter

@Produces

public OrderDao createDao(@MyQualifier OrderDao dao) { } // Producer

}

★JSFの管理BeanをCDIの管理Beanに変換

import javax.faces.bean.ManagedBean;

import javax.faces.bean.SessionScoped;

...

@ManagedBean(name = "xxx")

@SessionScoped

...

import javax.enterprise.context.SessionScoped;

import javax.inject.Named;

...

@Named

@SessionScoped

...

JSFの管理BeanよりCDIの管理Beanを利用するメリット:

・会話スコープが使える(特定の画面遷移の間のみメモリに保持)

・EJBを含めて他の任意のBeanを@Injectできる

・インジェクション対象が文字列ベースでなく、型ベースなので間違いにくい

・EJBそのものを管理BeanとしてJSFの画面から利用できる

@Name

public class MyBean {

public String getNumber() {

...

}

}

#{myBean.number}

@Name("bean")

public class MyBean {

public String getNumber() {

...

}

}

#{bean.number}

public class MyBean {

@Produces

@Name("number")

public String getNumber() {

...

}

}

#{number}

★Nonbindingについて

@Inject

@MyConfig(option=OPTION.X)

private MyClass myClass;

@Produces

@MyConfig

public MyClass create(InjectionPoint injectionPoint) {

MyConfig config =

injectionPoint.getAnnotated().getAnnotation(MyConfig.class);

MyClass myClass = new MyClass();

myClass.setOption(config.option());

return myClass;

}

@Qualifier

@Documented

@Retention(RUNTIME)

@Target({FIELD, METHOD, TYPE, PARAMETER})

public @interface MyConfig {

@Nonbinding String option() default OPTION.A;

}

★Portable extensions

javax.enterprise.inject.spi

BeforeBeanDiscovery

ProcessAnnotatedType

ProcessInjectionTarget and ProcessProducer

ProcessBean and ProcessObserverMethod

AfterBeanDiscovery

AfterDeploymentValidation

BeforeShutdown

JSF版

// Created when the application starts

@ManagedBean(eager=true)

@ApplicationScoped

public class LayoutController implements Serializable {

...

}

CDI版

@Eager

@Named

@ApplicationScoped

public class LayoutController implements Serializable {

...

}

META-INF/services/javax.enterprise.inject.spi.Extension

xxx.yyy.EagerExtension

★JavaEE 6のアノテーションに関する使い分け

シングルトン関連

@javax.ejb.Singleton(JSR-318、EJB3.1仕様)

セッションBeanの一種、EJBコンテナのトランザクションや同時実行制御の機能を使う

@javax.inject.Singleton(JSR-330、DI仕様)

@javax.enterprise.inject.ApplicationScoped(JSR-299、CDI仕様)

通常のEJBコンテナ

@javax.faces.bean.ApplicationScoped (JSR-314、JSF2.0仕様)

Tomcat+Springなどの環境(CDIを利用しない)でシングルトン的な管理Beanを定義する

管理Bean関連

@javax.annotation.ManagedBean(JSR-250、JSR-316)

サービス層で利用、EL式と互換性がない

@javax.faces.bean.ManagedBean(JSR-314、JSF2.0仕様)

JSFの管理Bean

スコープ関連

@javax.faces.bean.CustomScoped

@javax.faces.bean.RequestScoped

@javax.faces.bean.ViewScoped

@javax.faces.bean.SessionScoped

@javax.faces.bean.ApplicationScoped

※JSF2.0仕様の管理Bean

@javax.enterprise.context.RequestScoped

@javax.faces.view.ViewScoped

@javax.enterprise.context.ConversationScoped

@javax.faces.flow.FlowScoped

@javax.enterprise.context.SessionScoped

@javax.enterprise.context.ApplicationScoped

※CDI仕様の管理Bean

@RequestScoped

//@SessionScoped

//@ApplicationScoped

public class MyBean implements Serializable {

@PostConstruct

private void init(){

...

}

@PreDestroy

private void destroy(){

...

}

}

@Named

@ConversationScoped

public class MyController implements Serializable {

@Inject

private Conversation conversation;

public void begin() {

...

conversation.begin();

}

public void end() {

...

conversation.end();

}

}

ApplicationScoped ⇒ SessionScoped ⇒ RequestScopedの順で出力:

ApplicationScoped(PostConstruct)

SessionScoped(PostConstruct)

RequestScoped(PostConstruct)

RequestScoped(PreDestroy)

Server shutdown initiated

SessionScoped(PreDestroy)

ApplicationScoped(PreDestroy)

インジェクション関連

EJBのインジェクションについて、@EJBと@Injectを使う

@javax.annotation.ManagedBeanで定義された管理Beanに対して、@Resourceと@Injectを使う

CDIを利用するなら、@Injectに統一する

@EJBの用途はリモートインターフェースをインジェクションする

★Instanceについて

@Qualifier

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.TYPE)

public @interface MyQualifier {

}

public interface MyInterface {

void execute();

}

public class MyClass1 implements MyInterface {

@Override

public void execute() {

System.out.println("MyClass1");

}

}

@MyQualifier

public class MyClass2 implements MyInterface {

@Override

public void execute() {

System.out.println("MyClass2");

}

}

import javax.enterprise.inject.Any;

import javax.enterprise.inject.Instance;

import javax.inject.Inject;

@Inject

@Any

Instance<MyInterface> instance1;

@Inject

Instance<MyInterface> instance2;

public void execute() {

MyInterface obj;

// @Any あり

obj = instance1.select(MyClass1.class).get();

obj.execute(); //MyClass1

obj = instance1.select(MyClass2.class).get();

obj.execute(); //MyClass2

// @Any なし

obj = instance2.select(MyClass1.class).get();

obj.execute(); //MyClass1

obj = instance2.select(MyClass2.class).get();

obj.execute(); //例外

}

@Anyを付ける場合、Qualifierを無視

@Anyを付けない場合、Qualifierのないインスタンスのみが選択される

public interface MyService { ... }

public class MyImpl1 implements MyService { ... }

@Custom

public class MyImpl2 implements MyService { ... }

@Inject

MyService myService;

@Inject @Default

MyService myService;

@Inject @Any ×

MyService myService;

@Inject @Any @Custom

MyService myService;

@Inject @Any @Default

MyService myService;

@Inject @Custom

MyService myService;

他のメソッド

@Inject

@Any

Instance<MyInterface> instance;

if (instance.isUnsatisfied()) {

...

} else if (instance.isAmbiguous()) {

for (MyInterface i : instance) {

System.out.println("%s%n", i);

}

} else {

System.out.println(instance.get());

}

リスト

@Inject

public void listImplementations(

@Any Instance<Service> serviceList) {

for(Service service : serviceList){

System.out.println(service.getClass().getCanonicalName());

}

...

}

★BeanManagerについて

import javax.enterprise.inject.spi.BeanManager;

import javax.enterprise.inject.spi.Bean;

try {

BeanManager bm = InitialContext.doLookup("java:comp/BeanManager");

Set<Bean<?>> beans = bm.getBeans(MyInterface.class);

Bean<?> bean = bm.resolve(beans); // 一意の場合

CreationalContext<?> cc = bm.createCreationalContext(bean);

MyInterface i = (MyInterface) bm.getReference(bean, MyInterface.class, cc);

...

} catch (NamingException e) {

...

}

@Resource

private BeanManager beanManager;

Class<? extends XXX> type = ...;

Set<Bean<?>> beans = beanManager.getBeans(type);

Bean<?> bean = beanManager.resolve(beans);

CreationalContext<?> creationalContext = beanManager

.createCreationalContext(bean);

MyClass c = (MyClass) beanManager.getReference(bean, type, creationalContext);

★InjectionPointについて

サンプル1

@Retention(RUNTIME)

@Target(FIELD)

public @interface FtpHostname {

String value();

}

import javax.enterprise.inject.Produces;

import javax.enterprise.inject.spi.Annotated;

import javax.enterprise.inject.spi.InjectionPoint;

import org.apache.commons.net.ftp.FTPClient;

public class NetProducer {

@Produces

public FTPClient createFtpClient(InjectionPoint ip)

throws SocketException, IOException{

Annotated annotated = ip.getAnnotated();

FtpHostname hostname =

annotated.getAnnotation(FtpHostname.class);

FTPClient ftpClient = new FTPClient();

if(hostname != null){

ftpClient.connect(hostname.value());

}

return ftpClient;

}

}

@Inject

@FtpHostname(value = "ftp.domain.com")

private FTPClient ftpClient;

サンプル2

@Retention(RUNTIME)

@Target({FIELD, TYPE, METHOD})

public @interface NotificationServiceType {

ServiceType value();

public enum ServiceType{

EMAIL(EmailNotificationService.class),

SMS(SmsNotificationService.class);

Class<? extends NotificationService> clazz;

private ServiceType(Class<? extends NotificationService> clazz){

this.clazz = clazz;

}

public Class<? extends NotificationService> getClazz() {

return clazz;

}

}

}

import javax.enterprise.inject.Any;

import javax.enterprise.inject.Instance;

import javax.enterprise.inject.Produces;

import javax.enterprise.inject.spi.Annotated;

import javax.enterprise.inject.spi.InjectionPoint;

public class ServiceFactory {

@Produces

@MyService

public NotificationService createService(

@Any Instance<NotificationService> instance, InjectionPoint ip){

Annotated annotated = ip.getAnnotated();

NotificationServiceType type =

annotated.getAnnotation(NotificationServiceType.class);

Class<? extends NotificationService> clazz =

type.value().getClazz();

return instance.select(clazz).get();

}

}

@Qualifier

@Retention(RUNTIME)

@Target({FIELD, TYPE, METHOD})

public @interface MyService { }

@Inject

@MyService

@NotificationServiceType(ServiceType.SMS)

private NotificationService notificationService;

サンプル3 (Log)

import javax.enterprise.inject.Produces;

import javax.enterprise.inject.spi.InjectionPoint;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

public class Resources {

@Produces

public Logger loggerProducer(InjectionPoint ip){

return LoggerFactory.getLogger(

ip.getMember().getDeclaringClass().getName());

}

}

@Inject

private Logger logger;

// 従来

private Logger logger = LoggerFactory.getLogger(getClass().getName());

サンプル4 (JSF)

@HttpParam("username")

@Inject

private String username;

@HttpParam("password")

@Inject

private String password;

@Produces

@HttpParam("")

public String getParamValue(InjectionPoint ip) {

ServletRequest request =

(ServletRequest) FacesContext.getCurrentInstance()

.getExternalContext().getRequest();

return request.getParameter(

ip.getAnnotated().getAnnotation(HttpParam.class).value());

}

サンプル5 (Primitives)

@Singleton

@Startup

public class HitsTimer {

@Inject

private int hitsRate;

...

}

@Singleton

@Startup

public class Configuration {

private Map configuration;

private Set unconfiguredFields;

@PostConstruct

public void init() {

this.configuration = new HashMap();

this.unconfiguredFields = new HashSet();

}

@Produces

public String getString(InjectionPoint point) {

String fieldName = point.getMember().getName();

String value = configuration.get(fieldName);

if (value == null) {

this.unconfiguredFields.add(fieldName);

}

return value;

}

@Produces

public int getInteger(InjectionPoint point) {

String value = getString(point);

if (value == null) {

return 0;

}

return Integer.parseInt(value);

}

...

}