Chapter 7. Jakarta Contexts and Dependency Injection
7.1. Introduction to Jakarta Contexts and Dependency Injection
7.1.1. About Jakarta Contexts and Dependency Injection
Jakarta Contexts and Dependency Injection 2.0 is a specification designed to enable Jakarta Enterprise Beans 3 components to be used as Jakarta Server Faces managed beans. Jakarta Contexts and Dependency Injection unifies the two component models and enables a considerable simplification to the programming model for web-based applications in Java. Details about Jakarta Contexts and Dependency Injection 2.0 can be found in Jakarta Contexts and Dependency Injection 2.0 Specification.
JBoss EAP includes Weld, which is a Jakarta Contexts and Dependency Injection 2.0 compatible specification.
Weld is a compatible implementation of Jakarta Contexts and Dependency Injection for the Jakarta EE Platform. Jakarta Contexts and Dependency Injection is a Jakarta EE standard for dependency injection and contextual lifecycle management. Further, Jakarta Contexts and Dependency Injection is one of the most important parts of the Jakarta EE.
Benefits of Jakarta Contexts and Dependency Injection
The benefits of Jakarta Contexts and Dependency Injection include:
- Simplifying and shrinking your code base by replacing big chunks of code with annotations.
- Flexibility, allowing you to disable and enable injections and events, use alternative beans, and inject non-Contexts and Dependency Injection objects easily.
-
Optionally, allowing you to include a
beans.xml
file in yourMETA-INF/
orWEB-INF/
directory if you need to customize the configuration to differ from the default. The file can be empty. - Simplifying packaging and deployments and reducing the amount of XML you need to add to your deployments.
- Providing lifecycle management via contexts. You can tie injections to requests, sessions, conversations, or custom contexts.
- Providing type-safe dependency injection, which is safer and easier to debug than string-based injection.
- Decoupling interceptors from beans.
- Providing complex event notification.
7.2. Use Jakarta Contexts and Dependency Injection to develop an application
Jakarta Contexts and Dependency Injection gives you tremendous flexibility in developing applications, reusing code, adapting your code at deployment or runtime, and unit testing.
Weld comes with a special mode for application development. When enabled, certain built-in tools, which facilitate the development of Jakarta Contexts and Dependency Injection applications, are available.
The development mode should not be used in production as it can have a negative impact on the performance of the application. Make sure to disable the development mode before deploying to production.
Enabling the Development Mode for a Web Application:
For a web application, set the servlet initialization parameter org.jboss.weld.development
to true
:
<web-app> <context-param> <param-name>org.jboss.weld.development</param-name> <param-value>true</param-value> </context-param> </web-app>
Enabling Development Mode for JBoss EAP Using the Management CLI:
It is possible to enable the Weld development mode globally for all the applications deployed by setting development-mode
attribute to true
:
/subsystem=weld:write-attribute(name=development-mode,value=true)
7.2.1. Default Bean Discovery Mode
The default bean discovery mode for a bean archive is annotated
. Such a bean archive is said to be an implicit bean archive
.
If the bean discovery mode is annotated
, then:
-
Bean classes that do not have
bean defining annotation
and are not bean classes of sessions beans are not discovered. - Producer methods that are not on a session bean and whose bean class does not have a bean defining annotation are not discovered.
- Producer fields that are not on a session bean and whose bean class does not have a bean defining annotation are not discovered.
- Disposer methods that are not on a session bean and whose bean class does not have a bean defining annotation are not discovered.
- Observer methods that are not on a session bean and whose bean class does not have a bean defining annotation are not discovered.
All examples in the Contexts and Dependency Injection section are valid only when you have a discovery mode set to all
.
Bean Defining Annotations
A bean class can have a bean defining annotation
, allowing it to be placed anywhere in an application, as defined in bean archives. A bean class with a bean defining annotation is said to be an implicit bean.
The set of bean defining annotations contains:
-
@ApplicationScoped
,@SessionScoped
,@ConversationScoped
and@RequestScoped
annotations. - All other normal scope types.
-
@Interceptor
and@Decorator
annotations. -
All stereotype annotations, i.e. annotations annotated with
@Stereotype
. -
The
@Dependent
scope annotation.
If one of these annotations is declared on a bean class, then the bean class is said to have a bean defining annotation.
Example: Bean Defining Annotation
@Dependent public class BookShop extends Business implements Shop<Book> { ... }
To ensure compatibility with other JSR-330 implementations and the Jakarta Contexts and Dependency Injection specification, all pseudo-scope annotations, except @Dependent
, are not bean defining annotations. However, a stereotype annotation, including a pseudo-scope annotation, is a bean defining annotation.
7.2.2. Exclude Beans From the Scanning Process
Exclude filters are defined by <exclude>
elements in the beans.xml
file for the bean archive as children of the <scan>
element. By default an exclude filter is active. The exclude filter becomes inactive, if its definition contains:
-
A child element named
<if-class-available>
with aname
attribute, and the class loader for the bean archive can not load a class for that name, or -
A child element named
<if-class-not-available>
with aname
attribute, and the class loader for the bean archive can load a class for that name, or -
A child element named
<if-system-property>
with aname
attribute, and there is no system property defined for that name, or -
A child element named
<if-system-property>
with aname
attribute and a value attribute, and there is no system property defined for that name with that value.
The type is excluded from discovery, if the filter is active, and:
- The fully qualified name of the type being discovered matches the value of the name attribute of the exclude filter, or
- The package name of the type being discovered matches the value of the name attribute with a suffix ".*" of the exclude filter, or
- The package name of the type being discovered starts with the value of the name attribute with a suffix ".**" of the exclude filter
Example 7.1. Example: beans.xml
File
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"> <scan> <exclude name="com.acme.rest.*" /> 1 <exclude name="com.acme.faces.**"> 2 <if-class-not-available name="javax.faces.context.FacesContext"/> </exclude> <exclude name="com.acme.verbose.*"> 3 <if-system-property name="verbosity" value="low"/> </exclude> <exclude name="com.acme.ejb.**"> 4 <if-class-available name="javax.enterprise.inject.Model"/> <if-system-property name="exclude-ejbs"/> </exclude> </scan> </beans>
- 1
- The first exclude filter will exclude all classes in
com.acme.rest
package. - 2
- The second exclude filter will exclude all classes in the
com.acme.faces
package, and any subpackages, but only if Jakarta Server Faces is not available. - 3
- The third exclude filter will exclude all classes in the
com.acme.verbose
package if the system propertyverbosity
has the valuelow
. - 4
- The fourth exclude filter will exclude all classes in the
com.acme.ejb
package, and any subpackages, if the system propertyexclude-ejbs
is set with any value and if at the same time, thejavax.enterprise.inject.Model
class is also available to the classloader.
It is safe to annotate Jakarta EE components with @Vetoed
to prevent them being considered beans. An event is not fired for any type annotated with @Vetoed
, or in a package annotated with @Vetoed
. For more information, see @Vetoed
.
7.2.3. Use an Injection to Extend an Implementation
You can use an injection to add or change a feature of your existing code.
The following example adds a translation ability to an existing class, and assumes you already have a Welcome
class, which has a method buildPhrase
. The buildPhrase
method takes as an argument the name of a city, and outputs a phrase like "Welcome to Boston!".
This example injects a hypothetical Translator
object into the Welcome
class. The Translator
object can be a Jakarta Enterprise Beans stateless bean or another type of bean, which can translate sentences from one language to another. In this instance, the Translator
is used to translate the entire greeting, without modifying the original Welcome
class. The Translator
is injected before the buildPhrase
method is called.
Example: Inject a Translator Bean into the Welcome
Class
public class TranslatingWelcome extends Welcome { @Inject Translator translator; public String buildPhrase(String city) { return translator.translate("Welcome to " + city + "!"); } ... }
7.3. Ambiguous or Unsatisfied Dependencies
Ambiguous dependencies exist when the container is unable to resolve an injection to exactly one bean.
Unsatisfied dependencies exist when the container is unable to resolve an injection to any bean at all.
The container takes the following steps to try to resolve dependencies:
- It resolves the qualifier annotations on all beans that implement the bean type of an injection point.
-
It filters out disabled beans. Disabled beans are
@Alternative
beans which are not explicitly enabled.
In the event of an ambiguous or unsatisfied dependency, the container aborts deployment and throws an exception.
To fix an ambiguous dependency, see Use a Qualifier to Resolve an Ambiguous Injection.
7.3.1. Qualifiers
Qualifiers are annotations used to avoid ambiguous dependencies when the container can resolve multiple beans, which fit into an injection point. A qualifier declared at an injection point provides the set of eligible beans, which declare the same qualifier.
Qualifiers must be declared with a retention and target as shown in the example below.
Example: Define the @Synchronous
and @Asynchronous
Qualifiers
@Qualifier @Retention(RUNTIME) @Target({TYPE, METHOD, FIELD, PARAMETER}) public @interface Synchronous {}
@Qualifier @Retention(RUNTIME) @Target({TYPE, METHOD, FIELD, PARAMETER}) public @interface Asynchronous {}
Example: Use the @Synchronous
and @Asynchronous
Qualifiers
@Synchronous public class SynchronousPaymentProcessor implements PaymentProcessor { public void process(Payment payment) { ... } }
@Asynchronous public class AsynchronousPaymentProcessor implements PaymentProcessor { public void process(Payment payment) { ... } }
'@Any'
Whenever a bean or injection point does not explicitly declare a qualifier, the container assumes the qualifier @Default
. From time to time, you will need to declare an injection point without specifying a qualifier. There is a qualifier for that too. All beans have the qualifier @Any
. Therefore, by explicitly specifying @Any
at an injection point, you suppress the default qualifier, without otherwise restricting the beans that are eligible for injection.
This is especially useful if you want to iterate over all beans of a certain bean type.
import javax.enterprise.inject.Instance; ... @Inject void initServices(@Any Instance<Service> services) { for (Service service: services) { service.init(); } }
Every bean has the qualifier @Any
, even if it does not explicitly declare this qualifier.
Every event also has the qualifier @Any
, even if it was raised without explicit declaration of this qualifier.
@Inject @Any Event<User> anyUserEvent;
The @Any
qualifier allows an injection point to refer to all beans or all events of a certain bean type.
@Inject @Delegate @Any Logger logger;
7.3.2. Use a Qualifier to Resolve an Ambiguous Injection
You can resolve an ambiguous injection using a qualifier. Read more about ambiguous injections at Ambiguous or Unsatisfied Dependencies.
The following example is ambiguous and features two implementations of Welcome
, one which translates and one which does not. The injection needs to be specified to use the translating Welcome
.
Example: Ambiguous Injection
public class Greeter { private Welcome welcome; @Inject void init(Welcome welcome) { this.welcome = welcome; } ... }
Resolve an Ambiguous Injection with a Qualifier
To resolve the ambiguous injection, create a qualifier annotation called
@Translating
:@Qualifier @Retention(RUNTIME) @Target({TYPE,METHOD,FIELD,PARAMETERS}) public @interface Translating{}
Annotate your translating
Welcome
with the@Translating
annotation:@Translating public class TranslatingWelcome extends Welcome { @Inject Translator translator; public String buildPhrase(String city) { return translator.translate("Welcome to " + city + "!"); } ... }
Request the translating
Welcome
in your injection. You must request a qualified implementation explicitly, similar to the factory method pattern. The ambiguity is resolved at the injection point.public class Greeter { private Welcome welcome; @Inject void init(@Translating Welcome welcome) { this.welcome = welcome; } public void welcomeVisitors() { System.out.println(welcome.buildPhrase("San Francisco")); } }
7.4. Managed Beans
Jakarta EE establishes a common definition in the Jakarta Managed Beans specification. For Jakarta EE, managed beans are defined as container-managed objects with minimal programming restrictions, otherwise known by the acronym POJO (Plain Old Java Object). They support a small set of basic services, such as resource injection, lifecycle callbacks, and interceptors. Companion specifications, such as Jakarta Enterprise Beans and Jakarta Contexts and Dependency Injection, build on this basic model.
With very few exceptions, almost every concrete Java class that has a constructor with no parameters, or a constructor designated with the annotation @Inject
, is a bean. This includes every JavaBean and every Jakarta Enterprise Beans session bean.
7.4.1. Types of Classes That are Beans
A managed bean is a Java class. For Jakarta EE, the basic lifecycle and semantics of a managed bean are defined by the Jakarta Managed Beans 1.0 specification. You can explicitly declare a managed bean by annotating the bean class @ManagedBean
, but in Contexts and Dependency Injection you do not need to. According to the specification, the Contexts and Dependency Injection container treats any class that satisfies the following conditions as a managed bean:
- It is not a non-static inner class.
-
It is a concrete class or is annotated with
@Decorator
. -
It is not annotated with a Jakarta Enterprise Beans component-defining annotation or declared as a Jakarta Enterprise Beans bean class in the
ejb-jar.xml
file. -
It does not implement the interface
javax.enterprise.inject.spi.Extension
. -
It has either a constructor with no parameters, or a constructor annotated with
@Inject
. -
It is not annotated with
@Vetoed
or in a package annotated with@Vetoed
.
The unrestricted set of bean types for a managed bean contains the bean class, every superclass, and all interfaces it implements directly or indirectly.
If a managed bean has a public field, it must have the default scope @Dependent
.
@Vetoed
You can veto processing of a class so that no beans or observer methods defined by this class are installed:
@Vetoed public class SimpleGreeting implements Greeting { ... }
In this code, the SimpleGreeting
bean is not considered for injection.
All beans in a package can be prevented from injection:
@Vetoed package org.sample.beans; import javax.enterprise.inject.Vetoed;
This code in package-info.java
in the org.sample.beans
package will prevent all beans inside this package from injection.
Jakarta EE components, such as stateless Jakarta Enterprise Beans or Jakarta RESTful Web Services resource endpoints, can be marked with @Vetoed
to prevent them from being considered beans. Adding the @Vetoed
annotation to all persistent entities prevents the BeanManager
from managing an entity as a Jakarta Contexts and Dependency Injection Bean. When an entity is annotated with @Vetoed
, no injections take place. The reasoning behind this is to prevent the BeanManager
from performing the operations that might cause the Jakarta Persistence provider to break.
7.4.2. Use Contexts and Dependency Injection to Inject an Object Into a Bean
Contexts and Dependency Injection is activated automatically if Contexts and Dependency Injection components are detected in an application. If you want to customize your configuration to differ from the default, you can include a META-INF/beans.xml
file or a WEB-INF/beans.xml
file in your deployment archive.
Inject Objects into Other Objects
To obtain an instance of a class, annotate the field with
@Inject
within your bean:public class TranslateController { @Inject TextTranslator textTranslator; ...
Use your injected object’s methods directly. Assume that
TextTranslator
has a methodtranslate
:// in TranslateController class public void translate() { translation = textTranslator.translate(inputText); }
Use an injection in the constructor of a bean. You can inject objects into the constructor of a bean as an alternative to using a factory or service locator to create them:
public class TextTranslator { private SentenceParser sentenceParser; private Translator sentenceTranslator; @Inject TextTranslator(SentenceParser sentenceParser, Translator sentenceTranslator) { this.sentenceParser = sentenceParser; this.sentenceTranslator = sentenceTranslator; } // Methods of the TextTranslator class ... }
Use the
Instance(<T>)
interface to get instances programmatically. TheInstance
interface can return an instance ofTextTranslator
when parameterized with the bean type.@Inject Instance<TextTranslator> textTranslatorInstance; ... public void translate() { textTranslatorInstance.get().translate(inputText); }
When you inject an object into a bean, all of the object’s methods and properties are available to your bean. If you inject into your bean’s constructor, instances of the injected objects are created when your bean’s constructor is called, unless the injection refers to an instance that already exists. For instance, a new instance would not be created if you inject a session-scoped bean during the lifetime of the session.
7.5. Contexts and Scopes
A context, in terms of Contexts and Dependency Injection, is a storage area that holds instances of beans associated with a specific scope.
A scope is the link between a bean and a context. A scope/context combination can have a specific lifecycle. Several predefined scopes exist, and you can create your own. Examples of predefined scopes are @RequestScoped
, @SessionScoped
, and @ConversationScope
.
Scope | Description |
---|---|
|
The bean is bound to the lifecycle of the bean holding the reference. The default scope for an injected bean is |
| The bean is bound to the lifecycle of the application. |
| The bean is bound to the lifecycle of the request. |
| The bean is bound to the lifecycle of the session. |
| The bean is bound to the lifecycle of the conversation. The conversation scope is between the lengths of the request and the session, and is controlled by the application. |
Custom scopes | If the above contexts do not meet your needs, you can define custom scopes. |
7.6. Named Beans
You can name a bean by using the @Named
annotation. Naming a bean allows you to use it directly in Jakarta Server Faces and Jakarta Expression Language.
The @Named
annotation takes an optional parameter, which is the bean name. If this parameter is omitted, the bean name defaults to the class name of the bean with its first letter converted to lowercase.
7.6.1. Use Named Beans
Configure Bean Names Using the @Named Annotation
Use the
@Named
annotation to assign a name to a bean.@Named("greeter") public class GreeterBean { private Welcome welcome; @Inject void init (Welcome welcome) { this.welcome = welcome; } public void welcomeVisitors() { System.out.println(welcome.buildPhrase("San Francisco")); } }
In the example above, the default name would be
greeterBean
if no name had been specified.Use the named bean in a Jakarta Server Faces view.
<h:form> <h:commandButton value="Welcome visitors" action="#{greeter.welcomeVisitors}"/> </h:form>
7.7. Bean Lifecycle
This task shows you how to save a bean for the life of a request.
The default scope for an injected bean is @Dependent
. This means that the bean’s lifecycle is dependent upon the lifecycle of the bean that holds the reference. Several other scopes exist, and you can define your own scopes. For more information, see Contexts and Scopes.
Manage Bean Lifecycles
Annotate the bean with the desired scope.
@RequestScoped @Named("greeter") public class GreeterBean { private Welcome welcome; private String city; // getter & setter not shown @Inject void init(Welcome welcome) { this.welcome = welcome; } public void welcomeVisitors() { System.out.println(welcome.buildPhrase(city)); } }
When your bean is used in the Jakarta Server Faces view, it holds state.
<h:form> <h:inputText value="#{greeter.city}"/> <h:commandButton value="Welcome visitors" action="#{greeter.welcomeVisitors}"/> </h:form>
Your bean is saved in the context relating to the scope that you specify, and lasts as long as the scope applies.
7.7.1. Use a Producer Method
A producer method is a method that acts as a source of bean instances. When no instance exists in the specified context, the method declaration itself describes the bean, and the container invokes the method to obtain an instance of the bean. A producer method lets the application take full control of the bean instantiation process.
This section shows how to use producer methods to produce a variety of different objects that are not beans for injection.
Example: Use a Producer Method
By using a producer method instead of an alternative, polymorphism after deployment is allowed.
The @Preferred
annotation in the example is a qualifier annotation. For more information about qualifiers, see Qualifiers.
@SessionScoped public class Preferences implements Serializable { private PaymentStrategyType paymentStrategy; ... @Produces @Preferred public PaymentStrategy getPaymentStrategy() { switch (paymentStrategy) { case CREDIT_CARD: return new CreditCardPaymentStrategy(); case CHECK: return new CheckPaymentStrategy(); default: return null; } } }
The following injection point has the same type and qualifier annotations as the producer method, so it resolves to the producer method using the usual Contexts and Dependency Injection injection rules. The producer method is called by the container to obtain an instance to service this injection point.
@Inject @Preferred PaymentStrategy paymentStrategy;
Example: Assign a Scope to a Producer Method
The default scope of a producer method is @Dependent
. If you assign a scope to a bean, it is bound to the appropriate context. The producer method in this example is only called once per session.
@Produces @Preferred @SessionScoped public PaymentStrategy getPaymentStrategy() { ... }
Example: Use an Injection Inside a Producer Method
Objects instantiated directly by an application cannot take advantage of dependency injection and do not have interceptors. However, you can use dependency injection into the producer method to obtain bean instances.
@Produces @Preferred @SessionScoped public PaymentStrategy getPaymentStrategy(CreditCardPaymentStrategy ccps, CheckPaymentStrategy cps ) { switch (paymentStrategy) { case CREDIT_CARD: return ccps; case CHEQUE: return cps; default: return null; } }
If you inject a request-scoped bean into a session-scoped producer, the producer method promotes the current request-scoped instance into session scope. This is almost certainly not the desired behavior, so use caution when you use a producer method in this way.
The scope of the producer method is not inherited from the bean that declares the producer method.
Producer methods allow you to inject non-bean objects and change your code dynamically.
7.8. Alternative Beans
Alternatives are beans whose implementation is specific to a particular client module or deployment scenario.
By default, @Alternative
beans are disabled. They are enabled for a specific bean archive by editing its beans.xml
file. However, this activation only applies to the beans in that archive. You can enable alternative for the entire application using the @Priority
annotation.
Example: Defining Alternatives
This alternative defines an implementation of the PaymentProcessor
class using both @Synchronous
and @Asynchronous
alternatives:
@Alternative @Synchronous @Asynchronous public class MockPaymentProcessor implements PaymentProcessor { public void process(Payment payment) { ... } }
Example: Enabling @Alternative
Using beans.xml
<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_2_0.xsd"> <alternatives> <class>org.mycompany.mock.MockPaymentProcessor</class> </alternatives> </beans>
Declaring Selected Alternatives
The @Priority
annotation allows an alternative to be enabled for an entire application. An alternative can be given a priority for the application:
-
by placing the
@Priority
annotation on the bean class of a managed bean or session bean, or -
by placing the
@Priority
annotation on the bean class that declares the producer method, field or resource.
7.8.1. Override an Injection with an Alternative
You can use alternative beans to override existing beans. They can be thought of as a way to plug in a class which fills the same role, but functions differently. They are disabled by default.
This task shows you how to specify and enable an alternative.
Override an Injection
This task assumes that you already have a TranslatingWelcome
class in your project, but you want to override it with a "mock" TranslatingWelcome
class. This would be the case for a test deployment, where the true Translator
bean cannot be used.
Define the alternative.
@Alternative @Translating public class MockTranslatingWelcome extends Welcome { public String buildPhrase(string city) { return "Bienvenue à " + city + "!"); } }
Activate the substitute implementation by adding the fully-qualified class name to your
META-INF/beans.xml
orWEB-INF/beans.xml
file.<beans> <alternatives> <class>com.acme.MockTranslatingWelcome</class> </alternatives> </beans>
The alternative implementation is now used instead of the original one.
7.9. Stereotypes
In many systems, use of architectural patterns produces a set of recurring bean roles. A stereotype allows you to identify such a role and declare some common metadata for beans with that role in a central place.
A stereotype encapsulates any combination of:
- A default scope.
- A set of interceptor bindings.
A stereotype can also specify either:
- All beans where the stereotypes are defaulted bean EL names.
- All beans where the stereotypes are alternatives.
A bean can declare zero, one, or multiple stereotypes. A stereotype is an @Stereotype
annotation that packages several other annotations. Stereotype annotations can be applied to a bean class, producer method, or field.
A class that inherits a scope from a stereotype can override that stereotype and specify a scope directly on the bean.
In addition, if a stereotype has a @Named
annotation, any bean it is placed on has a default bean name. The bean can override this name if the @Named
annotation is specified directly on the bean. For more information about named beans, see Named Beans.
7.9.1. Use Stereotypes
Without stereotypes, annotations can become cluttered. This task shows you how to use stereotypes to reduce the clutter and streamline your code.
Example: Annotation Clutter
@Secure @Transactional @RequestScoped @Named public class AccountManager { public boolean transfer(Account a, Account b) { ... } }
Define and Use Stereotypes
Define the stereotype.
@Secure @Transactional @RequestScoped @Named @Stereotype @Retention(RUNTIME) @Target(TYPE) public @interface BusinessComponent { ... }
Use the stereotype.
@BusinessComponent public class AccountManager { public boolean transfer(Account a, Account b) { ... } }
7.10. Observer Methods
Observer methods receive notifications when events occur.
Contexts and Dependency Injection also provides transactional observer methods, which receive event notifications during the before completion or after completion phase of the transaction in which the event was fired.
7.10.1. Fire and Observe Events
Example: Fire an Event
The following code shows an event being injected and used in a method.
public class AccountManager { @Inject Event<Withdrawal> event; public boolean transfer(Account a, Account b) { ... event.fire(new Withdrawal(a)); } }
Example: Fire an Event with a Qualifier
You can annotate your event injection with a qualifier, to make it more specific. For more information about qualifiers, see Qualifiers.
public class AccountManager { @Inject @Suspicious Event <Withdrawal> event; public boolean transfer(Account a, Account b) { ... event.fire(new Withdrawal(a)); } }
Example: Observe an Event
To observe an event, use the @Observes
annotation.
public class AccountObserver { void checkTran(@Observes Withdrawal w) { ... } }
You can use qualifiers to observe only specific types of events.
public class AccountObserver { void checkTran(@Observes @Suspicious Withdrawal w) { ... } }
7.10.2. Transactional Observers
Transactional observers receive the event notifications before or after the completion phase of the transaction in which the event was raised. Transactional observers are important in a stateful object model because state is often held for longer than a single atomic transaction.
There are five kinds of transactional observers:
-
IN_PROGRESS
: By default, observers are invoked immediately. -
AFTER_SUCCESS
: Observers are invoked after the completion phase of the transaction, but only if the transaction completes successfully. -
AFTER_FAILURE
: Observers are invoked after the completion phase of the transaction, but only if the transaction fails to complete successfully. -
AFTER_COMPLETION
: Observers are invoked after the completion phase of the transaction. -
BEFORE_COMPLETION
: Observers are invoked before the completion phase of the transaction.
The following observer method refreshes a query result set cached in the application context, but only when transactions that update the Category tree are successful:
public void refreshCategoryTree(@Observes(during = AFTER_SUCCESS) CategoryUpdateEvent event) { ... }
Assume you have cached a Jakarta Persistence query result set in the application scope as shown in the following example:
import javax.ejb.Singleton; import javax.enterprise.inject.Produces; @ApplicationScoped @Singleton public class Catalog { @PersistenceContext EntityManager em; List<Product> products; @Produces @Catalog List<Product> getCatalog() { if (products==null) { products = em.createQuery("select p from Product p where p.deleted = false") .getResultList(); } return products; } }
Occasionally a Product
is created or deleted. When this occurs, you need to refresh the Product
catalog. But you must wait for the transaction to complete successfully before performing this refresh.
The following is an example of a bean that creates and deletes Products
triggers events:
import javax.enterprise.event.Event; @Stateless public class ProductManager { @PersistenceContext EntityManager em; @Inject @Any Event<Product> productEvent; public void delete(Product product) { em.delete(product); productEvent.select(new AnnotationLiteral<Deleted>(){}).fire(product); } public void persist(Product product) { em.persist(product); productEvent.select(new AnnotationLiteral<Created>(){}).fire(product); } ... }
The Catalog
can now observe the events after successful completion of the transaction:
import javax.ejb.Singleton; @ApplicationScoped @Singleton public class Catalog { ... void addProduct(@Observes(during = AFTER_SUCCESS) @Created Product product) { products.add(product); } void removeProduct(@Observes(during = AFTER_SUCCESS) @Deleted Product product) { products.remove(product); } }
7.11. Interceptors
Interceptors allow you to add functionality to the business methods of a bean without modifying the bean’s method directly. The interceptor is executed before any of the business methods of the bean. Interceptors are defined as part of the Jakarta Enterprise Beans specification.
Jakarta Contexts and Dependency Injection enhances this functionality by allowing you to use annotations to bind interceptors to beans.
Interception points
- Business method interception: A business method interceptor applies to invocations of methods of the bean by clients of the bean.
- Lifecycle callback interception: A lifecycle callback interceptor applies to invocations of lifecycle callbacks by the container.
- Timeout method interception: A timeout method interceptor applies to invocations of the Jakarta Enterprise Beans timeout methods by the container.
Enabling Interceptors
By default, all interceptors are disabled. You can enable the interceptor by using the beans.xml
descriptor of a bean archive. However, this activation only applies to the beans in that archive.
You can enable interceptors for the whole application using the @Priority
annotation.
Example: Enabling Interceptors in beans.xml
<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_2.0.xsd"> <interceptors> <class>org.mycompany.myapp.TransactionInterceptor</class> </interceptors> </beans>
Having the XML declaration solves two problems:
- It enables you to specify an ordering for the interceptors in your system, ensuring deterministic behavior.
- It lets you enable or disable interceptor classes at deployment time.
Interceptors enabled using @Priority
are called before interceptors enabled using the beans.xml
file.
Having an interceptor enabled by @Priority
and at the same time invoked by the beans.xml
file leads to a nonportable behavior. This combination of enablement should therefore be avoided in order to maintain consistent behavior across different Jakarta Contexts and Dependency Injection implementations.
7.11.1. Use Interceptors with Jakarta Contexts and Dependency Injection
Jakarta Contexts and Dependency Injection can simplify your interceptor code and make it easier to apply to your business code.
Without Jakarta Contexts and Dependency Injection, interceptors have two problems:
- The bean must specify the interceptor implementation directly.
- Every bean in the application must specify the full set of interceptors in the correct order. This makes adding or removing interceptors on an application-wide basis time-consuming and error-prone.
Using Interceptors with Jakarta Contexts and Dependency Injection
Define the interceptor binding type.
@InterceptorBinding @Retention(RUNTIME) @Target({TYPE, METHOD}) public @interface Secure {}
Mark the interceptor implementation.
@Secure @Interceptor public class SecurityInterceptor { @AroundInvoke public Object aroundInvoke(InvocationContext ctx) throws Exception { // enforce security ... return ctx.proceed(); } }
Use the interceptor in your development environment.
@Secure public class AccountManager { public boolean transfer(Account a, Account b) { ... } }
Enable the interceptor in your deployment, by adding it to the
META-INF/beans.xml
orWEB-INF/beans.xml
file.<beans> <interceptors> <class>com.acme.SecurityInterceptor</class> <class>com.acme.TransactionInterceptor</class> </interceptors> </beans>
The interceptors are applied in the order listed.
7.12. Decorators
A decorator intercepts invocations from a specific Java interface, and is aware of all the semantics attached to that interface. Decorators are useful for modeling some kinds of business concerns, but do not have the generality of interceptors. A decorator is a bean, or even an abstract class, that implements the type it decorates, and is annotated with @Decorator
. To invoke a decorator in a Jakarta Contexts and Dependency Injection application, it must be specified in the beans.xml
file.
Example: Invoke a Decorator Through beans.xml
<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_2_0.xsd"> <decorators> <class>org.mycompany.myapp.LargeTransactionDecorator</class> </decorators> </beans>
This declaration serves two main purposes:
- It enables you to specify an ordering for decorators in your system, ensuring deterministic behavior.
- It lets you enable or disable decorator classes at deployment time.
A decorator must have exactly one @Delegate
injection point to obtain a reference to the decorated object.
Example: Decorator Class
@Decorator public abstract class LargeTransactionDecorator implements Account { @Inject @Delegate @Any Account account; @PersistenceContext EntityManager em; public void withdraw(BigDecimal amount) { ... } public void deposit(BigDecimal amount); ... } }
You can enable decorator for the whole application using @Priority
annotation.
Decorators enabled using @Priority
are called before decorators enabled using the beans.xml
file. The lower priority values are called first.
Having a decorator enabled by @Priority
and at the same time invoked by beans.xml
, leads to a nonportable behavior. This combination of enablement should therefore be avoided in order to maintain consistent behavior across different Contexts and Dependency Injection implementations.
7.13. Portable Extensions
Contexts and Dependency Injection is intended to be a foundation for frameworks, extensions, and for integration with other technologies. Therefore, Contexts and Dependency Injection exposes a set of SPIs for the use of developers of portable extensions to Contexts and Dependency Injection.
Extensions can provide the following types of functionality:
- Integration with Business Process Management engines.
- Integration with third-party frameworks, such as Spring, Seam, GWT, or Wicket.
- New technology based upon the Contexts and Dependency Injection programming model.
According to the Jakarta Contexts and Dependency Injection specification, a portable extension can integrate with the container in the following ways:
- Providing its own beans, interceptors, and decorators to the container.
- Injecting dependencies into its own objects using the dependency. injection service.
- Providing a context implementation for a custom scope.
- Augmenting or overriding the annotation-based metadata with metadata from another source.
For more information, see Portable extensions in the Weld documentation.
7.14. Bean Proxies
Clients of an injected bean do not usually hold a direct reference to a bean instance. Unless the bean is a dependent object, scope @Dependent
, the container must redirect all injected references to the bean using a proxy object.
A bean proxy, which can be referred to as client proxy, is responsible for ensuring the bean instance that receives a method invocation is the instance associated with the current context. The client proxy also allows beans bound to contexts, such as the session context, to be serialized to disk without recursively serializing other injected beans.
Due to Java limitations, some Java types cannot be proxied by the container. If an injection point declared with one of these types resolves to a bean with a scope other than @Dependent
, the container aborts the deployment.
Certain Java types cannot be proxied by the container. These include:
- Classes that do not have a non-private constructor with no parameters.
-
Classes that are declared
final
or have afinal
method. - Arrays and primitive types.
7.15. Use a Proxy in an Injection
A proxy is used for injection when the lifecycles of the beans are different from each other. The proxy is a subclass of the bean that is created at runtime, and overrides all the non-private methods of the bean class. The proxy forwards the invocation onto the actual bean instance.
In this example, the PaymentProcessor
instance is not injected directly into Shop
. Instead, a proxy is injected, and when the processPayment()
method is called, the proxy looks up the current PaymentProcessor
bean instance and calls the processPayment()
method on it.
Example: Proxy Injection
@ConversationScoped class PaymentProcessor { public void processPayment(int amount) { System.out.println("I'm taking $" + amount); } } @ApplicationScoped public class Shop { @Inject PaymentProcessor paymentProcessor; public void buyStuff() { paymentProcessor.processPayment(100); } }