Slides of the university I gave at Devoxx Belgium with Antonio Goncalves on CDI, Java EE and JBoss Forge.
Abstract:
-------
During this 3 hours university, you will learn some CDI basis, and will quickly dive into more advance CDI features (such as extension). Using JBoss Forge we will quickly generate a Java EE 7 web application, and then, following business requirements, we will add CDI functionalities.
This university talk will be a mixture of code and slides, focusing on CDI and Java EE 7.
--------
Video of the university is available on YouTube: http://youtu.be/LYKMaj4XKvg
Code and Slides on GitHub: https://github.com/antoinesd/cdi-forge-uni/tree/DevoxxBe2015
15. Architecture - Model
User
String login
String password
String name
String email
UserRole role
Reimbursement
Date date
Set<Expense> expenses
Currency currency
User user
Conference conference
Expense
description
Date date
Float amount
ExpenseType expenseType
private Currency currency
Conference
String name
Date date
String country
String city
1
*
1 *
1
*
37. Declaring a Producer
public class ProducingBean {
@Produces
private List<Integer> mapInt = new ArrayList<>();
@Produces
@French
public List<String> FrenchListStrProducer() {
List<String> res = Arrays.asList("bonjour");
return res;
}
}
Producers should be declared in a Bean class
It’s a field or a method annotated with @Produces (static methods supported)
Producers can have qualifiers
value of field or method returned value is the bean instance
1
2
2
3
4
1
2
3
4
48. Interceptor example
@Interceptor
@Loggable
@Priority(Interceptor.Priority.APPLICATION)
public class LogInterceptor {
@AroundInvoke
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());
}
}
}
We make the class an interceptor ( @Interceptor comes from Interceptor spec)
We bind this interceptor to the @Loggable interceptor binding
We activate and give a priority to the interceptor (can be done in beans.xml config file as well)
This intercepting method will be invoked instead of the intercepted one.
1
2
3
4
1
2
3
4
49. Interceptor Usage
@Loggable
public class MyBean { ... }
public class MyOtherBean {
@Loggable
public String hello() { ... }
}
All bean’s method will be intercepted
This method will be intercepted
1
2
1
2
51. Decorator Example
@Decorator
@Priority(Interceptor.Priority.APPLICATION)
public abstract class HelloDecorator implements HelloService {
@Inject @Delegate HelloService service;
public String hello() {
return service.hello() + "-decorated";
}
}
Declares that the class is a decorator
Activates and gives a priority to the decorator (could be done via beans.xml file)
Decorators can be abstract and should share the same interface than decorated beans
Decorated bean is annotated with @Delegate (other beans could be injected in decorator)
Decorating method is called instead of decorated bean method. Delegate can be used in it.
1
2
3
4
5
1
2
3
4
5
52. Back to demo Create a @Loggable interceptor binding
Create a LoggingInterceptor
Add LoggingInterceptor to each service
Enable interceptor in the beans.xml
54. When an injection point resolves to multiple beans…
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.
59. Binding members limit the number of annotations to add to your
code
@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;
}
64. Examples
public class MyBean { ... }
@Named
public class MyBean2 { ... }
@Named @Language(FRENCH)
public class MyBean2 {
@Inject
MyBean2 bean;
}
this bean has @Default and @Any qualifiers
this bean has @Default , @Named and @Any qualifiers
this bean has @Language(FRENCH) , @Named and @Any qualifiers
this injection point has @Default qualifier
1
2
3
4
1
2
3
4
69. Back to demo Create a @Clear and @Encrypted qualifiers
Create a DigestPassword interface
Two implementations ClearPassword and EncryptPassword
Inject encrypted password digest into AccountBean and
UserService
71. Meet Instance interface
Instance interface lets perform typesafe resolution at runtime
public class MyBean {
@Inject
Instance<HelloService> service;
public void displayHello() {
display(service.get().hello());
}
}
Instance<T> injection points are always satisfied and never fail at deployment time
with Instance<T> you control when bean a instance is requested with the get() method
1
2
1
2
73. Looping on all beans in the 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());
}
}
}
78. Available scopes
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 session
Instance is created at first request and destroyed with its bound
context
CDI SPI allows third party to create their custom contexts
79. Examples
public class BaseHelloService implements HelloService { ... }
@RequestScoped
public class RequestService {
@Inject HelloService service;
}
@ApplicationScoped
public class ApplicationService {
@Inject RequestService service;
}
Bean has default scope @Dependent , instances are created for each injection
Bean is @RequestScoped . Instance is created by request context and destroyed with request context
Bean is @ApplicationScoped . Instance is created by application context and will live during all
application
No problem to inject bean from an other scope: CDI will provide the right bean
1
2
3
4
1
2
3
4
85. Example - Logout and session invalidation
@Inject FacesContext facesContext;
public String logout(){
try{
facesContext.getExternalContext().invalidateSession();
return "success?faces-redirect=true";
}
catch(Exception e){
return "error";
}
}
redirect is important to end current request and kill the session effectively
we might want to keep the current session and logout user
not a good practice to call UI layer from service to perform business
1
1
86. Example - Do logout in a CDI way
@Named
@SessionScoped
public class AccountBean implements Serializable {
@Inject
private Instance<AccountBean> myInstance;
public String doLogout() {
myInstance.destroy(myInstance.get());
return "/index";
}
Since CDI 1.1, Instance provides a destroy() method to remove an instance from its context
1
1
90. Alternative Example
public SapService implements ErpService {
}
@Alternative
public MockSapService implements ErpService {
}
@ApplicationScoped
public OrderService {
@Inject
ErpService service;
}
A service doing heavy or costly operation
Can be mocked with an alternative
Beans injecting the service have nothing to do. Alternative will replace the original bean if selected
1
2
3
1
2
3
91. Selecting the Alternative
With @Priority annotaion (for the whole app)
@Alternative
@Priority(Interceptor.Priority.APPLICATION)
public MockSapService implements ErpService {
}
With beans.xml file (for the current module)
<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">
<alternatives>
<class>com.acme.MockSapService</class>
</alternatives>
</beans>
1
93. Stereotypes
we have a lot of bean with @Conversation and @Named
we can create a stereotype gathering both
@Stereotype
@Named
@ConversationScoped
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface Wizard {
}
We are delcaring a Stereotype
All the annotations we want to put in the stereotype
1
2
2
1
2
96. Events in action
public class FirstBean {
@Inject Event<Post> postEvent;
public void saveNewPost(Post myPost) {
postEvent.fire(myPost);
}
}
public class SecondBean {
public void listenPost(@Observes Post post) {
System.out.println("Received : " + evt.message());
}
}
Event<T> is injected at the firing side. T is the event payload type (here my Post class)
When needed, we fire the event with our instance of T (here the Post object to save)
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.
1
2
3
1
2
3
97. Observer resolution mimics typesafe resolution (in a looser way)
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) {}
public void listenPost(@Observes Post post) {}
public void listenObject(@Observes Object obj) {}
public void listenEnPost(@Observes @English Post post) {}
}
These observers will be triggered (empty subset is accepted for qualifiers when resolving observers)
This observer won’t be triggered
1
1
1
2
1
2
98. Hooking on context lifecycle with events
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
105. Internal Step Happen Once Loop on Elements
Bean manager lifecycle
Deployment
Start
Before
Bean
Discovery
Scan
Archive
Process
Annotated
Type
After
Type
Discovery
Bean
Eligibility
Check
Process
Injection
Point
Process
Injection
Target
Process
Bean
Attributes
Process
Bean
Process
Producer
Process
Observer
Method
After
Bean
Discovery
After
Deployment
Validation
Application
Running
Before
Shutdown
Undeploy
Application
115. SPI for type meta-model
Annotated
Type getBaseType()
Set<Type> getTypeClosure()
<T extends Annotation> getAnnotation(Class<T>)
Set<Annotation> getAnnotations()
boolean isAnnotationPresent(Class<? extends Annotation>)
AnnotatedMember
X
Member getJavaMember()
boolean isStatic()
AnnotatedType<X> getDeclaringType()
AnnotatedParameter
X
int getPosition()
AnnotatedCallable<X> getDeclaringCallable()
AnnotatedType
X
Class<X> getJavaClass()
Set<AnnotatedConstructor<X>> getConstructors()
Set<AnnotatedMethod<? super X>> getMethods()
Set<AnnotatedField<? super X>> getFields()
AnnotatedCallable
X
List<AnnotatedParameter<X>> getParameters()
AnnotatedField
X
Field getJavaMember()
AnnotatedConstructor
X
Constructor<X> getJavaMember()
AnnotatedMethod
X
Method getJavaMember()
116. SPI dedicated to CDI meta-model
BeanAttributes
T
Set<Type> getTypes()
Set<Annotation> getQualifiers()
Class<? extends Annotation> getScope()
String getName()
Set<Class<? extends Annotation>> getStereotypes()
boolean isAlternative()
Bean
T
Class<?> getBeanClass()
Set<InjectionPoint> getInjectionPoints()
boolean isNullable()
Interceptor
T
Set<Annotation> getInterceptorBindings()
boolean intercepts(InterceptionType type)
Object intercept(InterceptionType, T, InvocationContext)
Decorator
T
Type getDelegateType()
Set<Annotation> getDelegateQualifiers()
Set<Type> getDecoratedTypes()
Producer
T
T produce(CreationalContext<T>)
void dispose(T)
Set<InjectionPoint> getInjectionPoints()
InjectionTarget
T
void inject(T, CreationalContext<T>)
void postConstruct(T)
void preDestroy(T)
InjectionPoint
Type getType()
Set<Annotation> getQualifiers()
Bean<?> getBean()
Member getMember()
Annotated getAnnotated()
boolean isDelegate()
boolean isTransient()
ObserverMethod
T
Class<?> getBeanClass()
Type getObservedType()
Set<Annotation> getObservedQualifiers()
Reception getReception()
TransactionPhase getTransactionPhase()
void notify(T)
EventMetadata
Set<Annotation> getQualifiers()
InjectionPoint getInjectionPoint()
Type getType()
124. Asynchronous event example
public class FirstBean {
@Inject Event<Post> postEvent;
public void saveNewPost(Post myPost) {
postEvent.fireAsync(myPost);
}
}
public class SecondBean {
public void listenPost(@ObservesAsync Post post) {
System.out.println("Received : " + evt.message());
}
}
We introduced a fireAsync() method
For backward compatibility we had to add an @ObservesAsync observer
1
2
1
2