JBoss Weld CDI for Java Platform (2013)
Chapter 2. Dependency Injection and Lookup
In this chapter, we will delve into the details of typesafe dependency injection, qualifiers, alternatives, client proxies, as well as provide insight into the rules that Weld uses to determine which bean to provide for each injection point. We'll finish up with how to programmatically retrieve beans directly from your application.
What is an injection point?
An injection point is identified by the @Inject annotation. Previously, we covered a nondefault constructor for a bean that was annotated with @Inject, as shown in the following code:
public class PaymentProcessor {
private final Payment payment;
@Inject
public PaymentProcessor(Payment payment) {
this.payment = payment;
}
}
This is known as bean constructor parameter injection and there can only be one constructor annotated with @Inject in a bean.
If a single constructor that defines every bean that we need to use, and thus needs to be injected, is not favored, there are two other ways to inject into our bean:
1. Create a bean that utilizes initializer method parameter injection, which has no restrictions on how many methods may be annotated with @Inject. If we were to change the PaymentProcessor class to use initializer method parameter injection, it would look like the following code snippet:
2. public class PaymentProcessor {
3. private final Payment payment;
4.
5. @Inject
6. void setPayment(Payment payment) {
7. this.payment = payment;
8. }
}
9. Create a bean that utilizes direct field injection, which also has no restrictions on the number of fields in a bean that have @Inject present. PaymentProcessor would now be:
10.public class PaymentProcessor {
11. @Inject
12. private Payment payment;
}
The major advantage of field over method injection is that it doesn't require any getter or setter methods to be present on the bean to perform the injection.
Tip
To create beans that are immutable, the best approach for a bean is to use either constructor or field injection.
The first time that a bean is instantiated by the container is the point when beans that match the injection points are injected ready for use.
The order in which a bean is constructed by the container is as follows:
1. Call the constructor of the bean to create an instance of the bean; this can either be the default constructor or one marked @Inject.
2. All fields of the bean marked @Inject will have their values initialized.
3. All initializer methods on the bean are called.
4. If a @PostConstruct method is present, it is called.
Note
The order in which initializer methods are called by the container is not defined by the CDI spec. Depending on the order in which they are called is not recommended as each implementation can use a different ordering.
There are three types of injection points that don't require the presence of @Inject: producer, observer, and disposer methods.
@Produces
PaymentProcessor createProcessor(Payment payment) {
return new PaymentProcessor(payment);
}
We'll cover observer methods as part of Chapter 7, Events.
Typesafe resolution
The CDI specification defines the process of matching a bean to an injection point as typesafe resolution. Bean type and qualifiers are the criterion used by the container to perform typesafe resolution for an application.
The process of typesafe resolution is usually performed by the container during application startup, making it possible for the container to end the startup process and warn the user if any beans have unsatisfied or unresolved ambiguous dependencies.
For a bean to be assignable to a given injection point, we need to make sure that:
· Its bean type matches the bean type of the injection point.
· It has all qualifiers that were specified on the injection point, and any member values of those qualifiers have the same value if the member is not annotated with @Nonbinding (we cover this in the next section). If no qualifiers are present on an injection point,@Default is assumed by the container.
Note
For the purpose of matching a bean type, a primitive type will match its corresponding wrapper type in java.lang and array types will match if their element types match.
The typesafe resolution process is designed in such a way that it allows more than one bean to implement the same bean type. This provides great flexibility to us by:
· The injection point selecting a specific implementation of a bean type through one or more qualifiers
· A deployment process selecting a specific implementation for a given deployment scenario, without application changes, by enabling or disabling an alternative through XML configuration
· Allowing beans to be divided into separate modules
Typically, when we begin developing a new application we will only have a single bean of each bean type we define. As we develop our application it becomes common place, and often necessary, to introduce various implementations for a bean type to satisfy the requirements of different runtime scenarios.
Qualifiers
A qualifier is an annotation that has @Qualifier declared on it, which signifies to the container that this qualifier annotation can be utilized on injection points and beans to distinguish different implementations of the same bean type.
A qualifier annotation, without any members, is just:
@Qualifier
@Retention(RUNTIME)
@Target( { TYPE, METHOD, FIELD, PARAMETER } )
public @interface MyQualifier {}
Note
@MyQualifier has been defined for use on a Java type, method, field, or method parameter.
We can use field injection with @MyQualifier by:
@Inject
@MyQualifier
private Locale myLocale;
Or constructor injection:
@Inject
public Notifications(@MyQualifier Locale myLocale) {
this.myLocale = myLocale;
}
Or an initializer method:
@Inject
public void setLocale(@MyQualifier Locale myLocale {
this.myLocale = myLocale;
}
Lastly, producing a Locale with the @MyQualifier annotation to match the injection points:
@Produces
@MyQualifier
public Locale getMyLocale() {
return Locale.US;
}
@Default and @Any
As we've mentioned previously, for any injection point that does not explicitly declare a qualifier annotation, the container will assume the presence of @Default, referred to as the default qualifier. The default qualifier is also assumed for any bean that does not declare one. If a bean contains the @Named annotation, it is still assumed to have @Default as @Named is not a qualifier annotation.
As the default qualifier is assumed to be present, both the beans in the following code are equivalent:
@Default
public class Ticket { ... }
public class Ticket { ... }
All beans of an application are given the @Any qualifier, whether or not there are other qualifier annotations specified. In our preceding example, Ticket would also have the @Any qualifier assumed by the container in both declarations.
@Any provides us the ability to retrieve all bean instances of a given bean type as it suppresses the addition of the default qualifier by the container.
If we had two beans of the same type, but a different set of qualifiers:
@Admin
public class Admin implements Account { ... }
public class User implements Account { ... }
We can retrieve all instances of beans that implement Account by:
@Inject
@Any
Instance<Account> accounts;
If we had forgotten to add @Any onto the preceding injection point, @Default would have been assumed by the container on the injection point and we would only have injected bean instances of type User.
Qualifier members
Qualifier annotations are also able to have members defined, which can be used as part of typesafe resolution. If a member is annotated with @Nonbinding, it will not be used during typesafe resolution and its value will have no meaning.
If we have the following qualifier annotation with members:
@Qualifier
@Retention(RUNTIME)
@Target( { TYPE, METHOD, FIELD, PARAMETER } )
public @interface Book {
Category value();
@Nonbinding
String description() default "";
}
We can then create a couple of different implementations such as:
@Book(Category.FICTION)
public class FictionSearch implements BookSearch {
}
@Book(value = Category.NONFICTION, description =
"These are nonfiction books.")
public class NonFictionSearch implements BookSearch {
}
We can also define an injection point that will inject the NonFictionSearch implementation:
@Inject
@Book(Category.NONFICTION)
BookSearch search;
We can see the impact of @Nonbinding by changing @Book to the following, and re-deploy the war to an application server with Weld.
@Qualifier
@Retention(RUNTIME)
@Target( { TYPE, METHOD, FIELD, PARAMETER } )
public @interface Book {
@Nonbinding
Category value();
@Nonbinding
String description() default "";
}
As opposed to having a NonFictionSearch bean injected, we receive a DeploymentException exception with the following message:
WELD-001409 Ambiguous dependencies for type [BookSearch] with
qualifiers [@Book] at injection point [[field] @Inject @Book
org.cdibook.chapter2.qualifiermembers.Search.search]. Possible
dependencies [[Managed Bean [class
org.cdibook.chapter2.qualifiermembers.FictionSearch] with
qualifiers [@Any @Book], Managed Bean [class
org.cdibook.chapter2.qualifiermembers.NonFictionSearch] with
qualifiers [@Any @Book]]]
It might look confusing, but the previous message from Weld is very descriptive about how it informs us that there is more than one bean that is eligible for injection into the search field on Search.
Alternatives
Alternatives are explicitly declared within beans.xml for them to be considered by the container for typesafe resolution, as they are disabled by default. One of the common uses of alternatives is for different deployment scenarios, such as for test deployments.
To create an alternative, a bean only requires the @Alternative annotation to be present on its type:
@Alternative
@Admin
@User
public class MockAccount implements Account { ... }
Activation of the alternative, for beans within the same archive, would need a beans.xml with:
<beans
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
<alternatives>
<class>org.cdibook.chapter2.alternatives.MockAccount</class>
</alternatives>
</beans>
Note
If an injection point has ambiguous dependencies during container startup, the container will look for any bean that is an alternative amongst the list of eligible beans. If there is only one alternative bean that is eligible, it will be injected instead of causing a deployment exception.
Resolving Weld deployment errors
Weld will abort the deployment of our application and provide helpful error messages in the server log, when it's unable to identify a single bean for each injection point with typesafe resolution. We would expect to see one or more of unsatisfied or ambiguous dependency errors in this situation.
An unsatisfied dependency occurs when there is not a single bean that is eligible for injection into an injection point. This can be resolved as follows:
· By creating a bean that implements the bean type of the injection point and declares all the qualifier annotations present at the injection point
· If we already have a bean of the correct bean type and all the qualifier annotations in our application, check whether the bean is on the classpath of the bean archive that contains the injection point with the error
· With beans.xml, enable an @Alternative bean of the correct bean type and qualifier annotations
An ambiguous dependency occurs when there is more than a single bean eligible for injection into an injection point. This can be resolved with the help of the following steps:
1. Add a new @Qualifier to disambiguate between the bean implementations that are eligible for injection.
2. Mark all but one of the bean implementations with @Alternative, thus disabling them by default.
3. Relocate the bean implementations into a separate bean archive that is not on the classpath of the injection point.
4. With beans.xml, disable all but one @Alternative bean that are eligible for injection to that injection point.
Note
There may be occasions where we do legitimately have an injection point with multiple beans that are eligible. In this instance, we need to use Instance as the bean type of the injection point. We will cover this in the last section of this chapter.
Client proxies
The reference to a bean injected into an injection point, or obtained by programmatic lookup, is usually not a direct reference to an instance of a bean, unless the injected bean is of @Dependent scope.
Instead of the actual bean instance, Weld injects a client proxy that is responsible for ensuring only the bean instance associated with the current context has a method invoked on it. That might sound confusing, but it will become clearer with an example.
@RequestScoped
public class RequestBean {
...
}
@ApplicationScoped
public class ApplicationBean {
@Inject
RequestBean bean;
}
Given the two beans we just defined, we would not want the same @RequestScoped bean to be used by all requests to our application, as there is only one instance of the @ApplicationScoped bean. The client proxy is injected into the @ApplicationScoped bean instead of an instance of the @RequestScoped bean and is responsible for retrieving the bean instance from the current request scope whenever a method is called. Through the client proxy, Weld is able to have two different requests using the same @ApplicationScoped bean, while calling methods on their respective @RequestScoped bean instances without us needing to do any special wiring or configuration.
A client proxy is also beneficial in situations where we have a bean in a scope that can be serialized to disk, such as @SessionScoped, and it has references to beans in a scope that can be retrieved at will, such as @ApplicationScoped. It certainly does not provide any benefit to serialize a bean instance that can be retrieved whenever it's needed, so the client proxy is serialized in its place.
Note
The client proxy being serialized to disk instead of the actual bean instance has the added benefit of not recursively serializing to disk a potentially large tree of bean instances that have references to other beans.
Unproxyable bean types
Due to limitations of the Java language, there are some legal bean types that are not able to have a client proxy created for them by the container. If an injection point tries to inject a bean type that is unable to be proxied, and it is not declared in the @Dependentscope, the container will abort deployment with an appropriate error message.
The following bean types are unable to have a client proxy created for them:
· Classes without a non-private constructor with no parameters, that is, a default constructor
· Classes declared final or with final methods
· Primitive types
· Array types
Here are some tips on how to resolve an unproxyable dependency error, such as the ones just mentioned:
· Add a default constructor to the bean type being injected
· Create an interface that can be implemented by the bean being injected and change the injection point to use the interface instead of the implementation
· If none of the previous work, we can set the scope to be @Dependent
Programmatic lookup of contextual instances
We may encounter some situations where it's not convenient to obtain a contextual instance through injection, which are as follows:
· Either the bean type or qualifiers of an injection point may vary at runtime
· In some deployments, there may be no bean that satisfies the bean type and qualifiers of an injection point
· We want to loop through all the beans of a specific bean type
For these situations we obtain an instance of Instance parameterized to the bean type we require:
@Inject
Instance<BookSearch> bookSearch;
To retrieve a contextual instance:
BookSearch search = bookSearch.get();
We can also alter the bean types that will be retrieved from Instance by adding qualifiers either to the injection point or passing them to select().
Specifying qualifiers at the injection is simple:
@Inject
@Book(Category.NONFICTION)
Instance<BookSearch> bookSearch;
But sometimes it's necessary for the qualifiers to be specified dynamically. For us to use dynamic qualifiers we need to suppress the @Default qualifier by specifying @Any on the injection point:
@Inject
@Any
Instance<BookSearch> bookSearch;
Now, we need to create an instance of our annotation @Book so it can be passed to select(). However, as it's just an interface we aren't able to create one with new, we need an implementation of that interface. CDI helps us out by providing AnnotationLiteral, a helper class for creating annotation implementations. Our annotation implementation is:
public class BookLiteral extends
AnnotationLiteral<Book> implements Book {
private final Category category;
private final String description;
public BookLiteral(Category category, String description) {
this.category = category;
this.description = description;
}
public Category value() {
return category;
}
public String description() {
return description;
}
}
So retrieving a bean from Instance with a dynamic qualifier is:
Annotation qualifier = fiction ? new BookLiteral(Category.FICTION,
"") : new BookLiteral(Category.NONFICTION, "Non Fiction");
BookSearch search = bookSearch.select(qualifier).get();
If we had separate qualifiers for fiction and nonfiction books, such as @NonFiction and @Fiction, instead of @Book with a Category, we could use dynamic qualifiers without extending AnnotationLiteral by creating anonymous classes:
bookSearch.select(new AnnotationLiteral<NonFiction>() {}).get();
Tip
"You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com . If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you."
Injection point metadata
There is lots of useful metadata information that is present on an injection point, which is represented in the javax.enterprise.inject.spi.InjectionPoint interface. Weld provides an implementation of InjectionPoint with @Dependent scope and @Default qualifier for us to retrieve the metadata.
The injection point provides the following functions:
· getBean(): This returns the Bean object of the bean defined on the injection point
· getType(): This returns the bean type of the injection point
· getQualifiers(): This returns all the qualifiers of the injection point
· getMember(): This returns a different instance depending on whether the injection point utilizes field injection (Field), method parameter injection (Method), or constructor parameter injection (Constructor)
· getAnnotated(): This returns AnnotatedField for field injection or AnnotatedParameter for method and constructor parameter injection
When we have an @Dependent scoped bean, there are occasions when it needs to retrieve metadata about the injection point to be properly constructed. A typical example of this requirement is with a logger:
class LoggerFactory {
@Produces
Logger createLogger(InjectionPoint point) {
return Logger.getLogger(point.getMember().getDeclaringClass().getName());
}
}
Injecting a logger with the name set to that of the class is then:
@Inject
Logger log;
Writing that simple producer has saved us time in not needing to specifically retrieve the class name to set it on a logger in every class we want to use it.
Summary
This chapter explored how dependency injection works with CDI containers through typesafe resolution, to help us understand bean types and qualifiers in determining which bean instance will be injected into an injection point. We covered some built-in qualifiers from the container before we created some of our own qualifiers, with and without members.
Injection points were explained with respect to typesafe resolution, before we looked at the metadata associated with an injection point and how that can be used when creating a bean instance.
We also covered creating alternatives and how they are activated within beans.xml, programmatically retrieving a bean instance at runtime for greater control, how Weld uses a proxy for injecting the non @Dependent beans, and how to resolve types that cannot be proxied.