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);
}
...
}