- Antoine Sabot-Durand
- Red Hat
- CDI spec lead
- @antoine_sd
- next-presso.com
- github.com/antoinesd
Slides here j.mp/introcdi |
Contexts and Dependency Injection
Java SE is supported by impls and will be at spec level in 2.0
CDI Provides:
CDI brings a powerful and modern programming model that standardizes good practices.
CDI provides a consistent and seamless way to integrate 3rd party frameworks to this model.
Once adopted, CDI will become the invisible backbone of our projects.
The container is the heart of CDI |
The container checks all possible CDI code at boot time |
The container manages your components lifecycle and services |
Yet, you’ll never see it (except in advanced development) |
Container is your application’s invisible conductor
During boot time, the container scans each class to check if it meets the conditions to be a bean |
These conditions are very loose so, most pojo classes are beans |
public class HelloService { (1)
public String hello() {
return "Hello World!";
}
}
1 | HelloService is a concrete class with a default constructor, therefore it’s a bean |
A bean can inject other beans or be injected into other beans |
public class MyBean {
@Inject (1)
private HelloService service; (2)
public void displayHello() {
display(service.hello();
}
}
1 | @Inject annotation defines an injection point (here a field) |
2 | The container looksa for the bean to inject by its type (here HelloService ) |
To become a bean a class must have a constructor with no parameters or a constructor annotated @Inject |
public class MyBean {
private HelloService service;
@Inject
private MyBean(HelloService service) {
this.service = service;
}
}
Only one constructor can be annotated with @Inject |
Such method is called initializer methods |
public class MyBean {
private HelloService service;
@Inject
public void initService(HelloService service) {
this.service = service;
}
}
all initializer methods are called at bean instantiation |
A bean support standard lifecycle callbacks |
public class MyBean {
@Inject
private HelloService service;
@PostConstruct (1)
public void init() { ... }
@PreDestroy (2)
public void bye() { ... }
}
1 | @PostConstruct is called after all injections are done |
2 | @PreDestroy is called before destroying the instance |
EJB session beans, Interceptors and Decorators are also CDI beans |
You can create your own injection target with CDI SPI |
A lot of Java EE Components support @Inject : |
If no bean is eligible for it, the injection point is unsatisfied |
If multiple beans are eligible for it, the injection point is ambiguous |
In both case a DeploymentException is thrown by the container |
Solving ambiguous cases with
public class MyBean {
@Inject
HelloService service;
}
public interface HelloService {
public String hello();
}
public class FrenchHelloService implements HelloService {
public String hello() {
return "Bonjour tout le monde!";
}
}
public class EnglishHelloService implements HelloService {
public String hello() {
return "Hello World!";
}
}
deployment fails with an Ambiguous dependencies error. |
@Qualifier
@Retention(RUNTIME)
@Target({FIELD, TYPE, METHOD, PARAMETER})
public @interface French {
}
@Qualifier
@Retention(RUNTIME)
@Target({FIELD, TYPE, METHOD, PARAMETER})
public @interface English {
}
Qualifiers are annotations annotated with @Qualifier |
@French
public class FrenchHelloService implements HelloService {
public String hello() {
return "Bonjour tout le monde!";
}
}
@English
public class EnglishHelloService implements HelloService {
public String hello() {
return "Hello World!";
}
}
public class MyBean {
@Inject
@French
HelloService serviceFr;
@Inject
@English
HelloService serviceEn;
}
Qualifiers are types enhancing injection, yet keeping it strong typed |
@Qualifier
@Retention(RUNTIME)
@Target({FIELD, TYPE, METHOD, PARAMETER})
public @interface Language {
LangChoice value();
@Nonbinding (1)
String description() default "";
public enum LangChoice {
FRENCH, ENGLISH
}
}
1 | @Nonbinding member’s value isn’t used in bean resolution mechanism |
@Language(FRENCH)
public class FrenchHelloService implements HelloService {
public String hello() {
return "Bonjour tout le monde!";
}
}
@Language(ENGLISH)
public class EnglishHelloService implements HelloService {
public String hello() {
return "Hello World!";
}
}
public class MyBean {
@Inject
@Language(value = FRENCH, description = "ici on parle français")
HelloService serviceFr;
@Inject
@Language(value = ENGLISH, description = "english spoken here")
HelloService serviceEn;
}
A bean can have multiple qualifiers |
@Language(FRENCH) @Console (1)
public class FrenchHelloService implements HelloService {
public String hello() {
return "Bonjour tout le monde!";
}
}
public class MyBean {
@Inject
@Language(FRENCH) @Console
HelloService serviceFr;
}
1 | @Console is a qualifier |
Injection point can have a non empty subset of the bean’s qualifiers |
@Language(FRENCH) @Console
public class FrenchHelloService implements HelloService {
public String hello() {
return "Bonjour tout le monde!";
}
}
public class MyBean {
@Inject
@Console
HelloService serviceFr;
}
Injection point can’t have a super set of bean’s qualifier |
@Language(FRENCH) @Console
public class FrenchHelloService implements HelloService {
public String hello() {
return "Bonjour tout le monde!";
}
}
public class MyBean {
@Inject
@Language(FRENCH) @Console @Language(CANADIAN) (1)
HelloService serviceFr;
}
1 | Unsatisfied injection point: deployment fails |
@Named set bean name for weak typed environment (EL, Javascript) |
@Default added to beans without qualifier or having only @Named |
@Any added to all beans for programmatic lookup and decorators |
@Initialized to qualify events when a context is started |
@Destroyed to qualify events when a context is destroyed |
public class MyBean { ... } (1)
@Named
public class MyBean2 { ... } (2)
@Named @Language(FRENCH) (3)
public class MyBean2 {
@Inject (4)
MyBean2 bean;
}
1 | this bean has @Default and @Any qualifiers |
2 | this bean has @Default , @Named and @Any qualifiers |
3 | this bean has @Language(FRENCH) , @Named and @Any qualifiers |
4 | this injection point has @Default qualifier |
When you don’t own your bean’s class, use
public class ProducingBean { (1)
@Produces (2)
private List<Integer> mapInt = new ArrayList<>();
@Produces (2)
@French (3)
public List<String> FrenchListStrProducer() { (4)
List<String> res = Arrays.asList("bonjour");
return res;
}
}
1 | Producers should be declared in a Bean class |
2 | It’s a field or a method annotated with @Produces (static methods supported) |
3 | Producers can have qualifiers |
4 | value of field or method returned value is the bean instance |
they can have qualifiers like any other bean |
producer method (or field) is called to create the bean instance |
public class MyBean {
@Inject
@French
private List<String> frenchListStrBean;
@Inject
public MyBean(List<Integer> initValues) {
...
}
}
Disposers can free resources when produced bean is destroyed |
public class ProducingBean {
@Produces
public MyNonCDIClass myProducer() {
return new MyNonCdiClass();
}
public void releaseMyInstance(@Disposes MyNonCdiClass inst) { (1)
inst.clean();
}
}
1 | Will be called at the bean instance end of life |
See them as @Predestroy
for Producers
Parameters will be injected by the container at instantiation time |
They should have matching beans otherwise deployment fails |
public class ProducingBean {
@Produces
public MyNonCDIClass listStrProducer(MyBean bean) { (1)
...
}
}
1 | The container resolves MyBean and passes it to the producer when an instance of the bean is requested |
The container can provide metadata about the Injection Point |
public class ProducingBean {
@Produces
public Logger produceLog(InjectionPoint injectionPoint) { (1)
return Logger.getLogger(injectionPoint.getMember()
.getDeclaringClass().getName());
}
}
1 | InjectionPoint is a SPI interface. The container can inject it to give info about the injection point |
InjectionPoint
can also be injected in managed bean
Putting the right a bean at the right place
A bean has a set of types depending on its definition |
Bean types set for a managed bean contains the bean class, every superclass and all interfaces it implements directly or indirectly. |
Bean types can be restricted by using @Typed annotation (Object is always in the set) |
public interface HelloService extends Hello { ... }
public class MyBean { (1)
@Produces
public HelloService prodHelloService() { ... } (2)
}
public class FrHelloService extends GenericService<String> implements HelloService { ... } (3)
@Typed({HelloService.class})
public class FrHelloService extends GenericService<String> implements HelloService { ... } (4)
1 | class MyBean and Object |
2 | HelloService , Hello and Object |
3 | FrHelloService , GenericService<String> , HelloService , Hello and Object |
4 | HelloService and Object |
Parameterized types information is kept (i.e. no type erasure) |
public class MyBean {
@Inject
Service<User> userService;
@Inject
Service<Staff> staffService;
}
Injection points above can be satisfied without ambiguity |
When resolving a bean to be injected to an injection point… |
…the container considers bean type and qualifiers. |
If the bean has a bean type that matches injection point type… |
…and the bean has all the qualifiers of the injection point… |
…the bean is assignable to the injection point. |
Resolving beans at runtime
Instance
interfaceInstance interface lets perform typesafe resolution at runtime |
public class MyBean {
@Inject
Instance<HelloService> service; (1)
public void displayHello() {
display(service.get().hello()); (2)
}
}
1 | Instance<T> injection points are always satisfied and never fail at deployment time |
2 | with Instance<T> you control when bean a instance is requested with the get() method |
Instance<T> contains methods to test bean resolution |
public class MyBean {
@Inject
Instance<HelloService> service;
public void displayHello() {
if (!(service.isUnsatisfied() || service.isAmbiguous())) { (1)
display(service.get().hello());
}
}
}
1 | if tou skip the test get() may throw an exception |
Instance
Instance<T> is iterable |
It’s where we use the @Any qualifier present on all beans |
public class MyBean {
@Inject
@Any
Instance<HelloService> services;
public void displayHello() {
for (HelloService service : services) {
display(service.hello());
}
}
}
AnnotationLiteral is a helper class to create an annotation instance |
public class MyBean {
@Inject
@Any
Instance<HelloService> services;
public void displayHello() {
display(
services.select(new AnnotationLiteral()<French> {}).get()); (1)
}
}
1 | select() also accepts a type. |
you can use TypeLiteral to create instances of parameterized types |
Beans life and death
All Beans have a scope defined by an annotation |
Each scope is associated to a context object |
So each bean is bound to a context |
The context is in charge of creating bean instances |
The Container is in charge of creating and destroying contexts |
CDI provides the following built-in scopes (and associated contexts): |
@Dependent
(default) bean has the same scope than its injector@ApplicationScoped
instance is linked to application lifecycle@SessionScoped
instance is linked to http session lifecycle@RequestScoped
instance is liked to http request lifecycle@ConversationScoped
lifecycle manually controlled within sessionInstance is created at first request and destroyed with its bound context |
CDI SPI allows third party to create their custom contexts |
public class BaseHelloService implements HelloService { ... } (1)
@RequestScoped (2)
public class RequestService {
@Inject HelloService service;
}
@ApplicationScoped (3)
public class ApplicationService {
@Inject RequestService service; (4)
}
1 | Bean has default scope @Dependent , instances are created for each injection |
2 | Bean is @RequestScoped . Instance is created by request context and destroyed with request context |
3 | Bean is @ApplicationScoped . Instance is created by application context and will live during all application |
4 | No problem to inject bean from an other scope: CDI will provide the right bean |
instances are created when first accessed not with their context |
A bean instance is a singleton in its context |
With SPI you can manually destroy an instance in a context |
A context can be inactive without being destroyed |
Request context is more than a mapping to ServletRequest lifecycle |
More decoupling with
public class FirstBean {
@Inject Event<Post> postEvent; (1)
public void saveNewPost(Post myPost) {
postEvent.fire(myPost); (2)
}
}
public class SecondBean {
public void listenPost(@Observes Post post) { (3)
System.out.println("Received : " + evt.message());
}
}
1 | Event<T> is injected at the firing side. T is the event payload type (here my Post class) |
2 | When needed, we fire the event with our instance of T (here the Post object to save) |
3 | At the consuming side, we define an observer for a given payload type with @Observes annotation.
If observed type match fired event type, the observer is called. |
public class FirstBean {
@Inject Event<Post> postEvent;
public void saveNewPost(Post myPost) {
postEvent.select(new AnnotationLiteral()<French> {}).fire(myPost);
}
}
public class SecondBean {
public void listenFrPost(@Observes @French Post post) {} (1)
public void listenPost(@Observes Post post) {} (1)
public void listenObject(@Observes Object obj) {} (1)
public void listenEnPost(@Observes @English Post post) {} (2)
}
1 | These observers will be triggered (empty subset is accepted for qualifiers when resolving observers) |
2 | This observer won’t be triggered |
public class SecondBean {
public void beginRequest(@Observes @Initialized(RequestScoped.class) ServletRequest req) {}
public void endRequest(@Observes @Destroyed(RequestScoped.class) ServletRequest req) {}
public void beginSession(@Observes @Initialized(SessionScoped.class) HttpSession session) {}
public void endSession(@Observes @Destroyed(SessionScoped.class) HttpSession session) {}
public void beginApplication(@Observes @Initialized(ApplicationScoped.class) ServlerContext ctx) {}
public void endApplication(@Observes @Destroyed(ApplicationScoped.class) ServlerContext ctx) {}
}
Observer must be defined in a bean |
Observer resolution may trigger bean instance creation. |
So observers above are a nice way to initailize bean with its context |
They are both Aspect Oriented Programming tools |
Interceptors are technical oriented: transaction, security, logging |
Interceptors are bound to any bean or bean method |
Decorators are business oriented: change the behaviour of a bean |
Decorators are bound to a bean type |
Interceptors are defined in their own specification |
User should create an annotation as an interceptor binding |
Interceptor class must be bound to an interceptor binding |
Interceptor class contains intercepting methods which are bound to lifecycle phase of intercepted bean |
Interceptor binding is added to intercepted bean or method |
An interceptor binding is annotation used in two different places |
On the interceptor and on the intercepted element to link them |
@InterceptorBinding (1)
@Target({METHOD, TYPE})
@Retention(RUNTIME)
public @interface Loggable {}
1 | This annotation comes from interceptor specification. It makes @Loggable an interceptor binding |
@Interceptor (1)
@Loggable (2)
@Priority(Interceptor.Priority.APPLICATION) (3)
public class LogInterceptor {
@AroundInvoke (4)
public Object log(InvocationContext ic) throws Exception {
System.out.println("Entering " + ic.getMethod().getName());
try {
return ic.proceed();
} finally {
System.out.println("Exiting " + ic.getMethod().getName());
}
}
}
1 | We make the class an interceptor (@Interceptor comes from Interceptor spec) |
2 | We bind this interceptor to the @Loggable interceptor binding |
3 | We activate and give a priority to the interceptor (can be done in beans.xml config file as well) |
4 | This intercepting method will be invoked instead of the intercepted one. |
@Loggable (1)
public class MyBean { ... }
public class MyOtherBean {
@Loggable (2)
public String hello() { ... }
}
1 | All bean’s method will be intercepted |
2 | This method will be intercepted |
To be decorated, a bean should implement an interface |
The decorator has to implement the same interface |
It is declared with @Decorator annotation |
It can be an abstract class (lets you choose methods to decorate) |
It injects the decorated bean with @Delegate annotation |
@Decorator (1)
@Priority(Interceptor.Priority.APPLICATION) (2)
public abstract class HelloDecorator implements HelloService { (3)
@Inject @Delegate HelloService service; (4)
public String hello() { (5)
return service.hello() + "-decorated";
}
}
1 | Declares that the class is a decorator |
2 | Activates and gives a priority to the decorator (could be done via beans.xml file) |
3 | Decorators can be abstract and should share the same interface than decorated beans |
4 | Decorated bean is annotated with @Delegate (other beans could be injected in decorator) |
5 | Decorating method is called instead of decorated bean method. Delegate can be used in it. |
Stereotypes creating annotation gathering multiple annotations |
Alternatives replace a bean for tests or specific environments |
Specialization bean overriding |
Transactional events binding events to specific transaction phase |
Check the spec on cdi-spec.org |
Slides are accessible here j.mp/introcdi |
Slides source github.com/antoinesd/Introduction-to-CDI |
Slides generated with Asciidoctor and DZSlides backend |
Original slide template - Dan Allen & _Sarah White |
License of this doc: CC BY-SA 4.0 |