Specialization, inheritance and alternatives
When you first start developing with CDI, you’ll likely be dealing only with a single bean implementation for each bean type. In this case, it’s easy to understand how beans get selected for injection. As the complexity of your application grows, multiple occurrences of the same bean type start appearing, either because you have multiple implementations or two beans share a common (Java) inheritance. That’s when you have to begin studying the specialization, inheritance and alternative rules to work through unsatisfied or ambiguous dependencies or to avoid certain beans from being called.
The CDI specification recognizes two distinct scenarios in which one bean extends another:
The second bean specializes the first bean in certain deployment scenarios. In these deployments, the second bean completely replaces the first, fulfilling the same role in the system.
The second bean is simply reusing the Java implementation, and otherwise bears no relation to the first bean. The first bean may not even have been designed for use as a contextual object.
The second case is the default assumed by CDI. It’s possible to have two beans in the system with the same part bean type (interface or parent class). As you’ve learned, you select between the two implementations using qualifiers.
The first case is the exception, and also requires more care. In any
given deployment, only one bean can fulfill a given role at a time. That
means one bean needs to be enabled and the other disabled. There are a
two modifiers involved: @Alternative
and @Specializes
. We’ll start
by looking at alternatives and then show the guarantees that
specialization adds.
Using alternative stereotypes
CDI lets you override the implementation of a bean type at deployment
time using an alternative. For example, the following bean provides a
default implementation of the PaymentProcessor
interface:
public class DefaultPaymentProcessor
implements PaymentProcessor {
...
}
But in our staging environment, we don’t really want to submit payments
to the external system, so we override that implementation of
PaymentProcessor
with a different bean:
public @Alternative
class StagingPaymentProcessor
implements PaymentProcessor {
...
}
or
public @Alternative
class StagingPaymentProcessor
extends DefaultPaymentProcessor {
...
}
We’ve already seen how we can enable this alternative by listing its
class in the beans.xml
descriptor.
But suppose we have many alternatives in the staging environment. It
would be much more convenient to be able to enable them all at once. So
let’s make @Staging
an @Alternative
stereotype and annotate the
staging beans with this stereotype instead. You’ll see how this level of
indirection pays off. First, we create the stereotype:
@Alternative
@Stereotype
@Retention(RUNTIME)
@Target(TYPE)
public @interface Staging {}
Then we replace the @Alternative
annotation on our bean with
@Staging
:
@Staging
public class StagingPaymentProcessor
implements PaymentProcessor {
...
}
Finally, we activate the @Staging
stereotype in the beans.xml
descriptor:
<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>
<stereotype>org.mycompany.myapp.Staging</stereotype>
</alternatives>
</beans>
Now, no matter how many staging beans we have, they will all be enabled at once.
A minor problem with alternatives
When we enable an alternative, does that mean the default implementation
is disabled? Well, not exactly. If the default implementation has a
qualifier, for instance @LargeTransaction
, and the alternative does
not, you could still inject the default implementation.
@Inject @LargeTransaction PaymentProcessor paymentProcessor;
So we haven’t completely replaced the default implementation in this deployment of the system. The only way one bean can completely override a second bean at all injection points is if it implements all the bean types and declares all the qualifiers of the second bean. However, if the second bean declares a producer method or observer method, then even this is not enough to ensure that the second bean is never called! We need something extra.
CDI provides a special feature, called specialization, that helps the developer avoid these traps. Specialization is a way of informing the system of your intent to completely replace and disable an implementation of a bean.
Using specialization
When the goal is to replace one bean implementation with a second, to help prevent developer error, the first bean may:
-
directly extend the bean class of the second bean, or
-
directly override the producer method, in the case that the second bean is a producer method, and then
explicitly declare that it specializes the second bean:
@Specializes
public class MockCreditCardPaymentProcessor
extends CreditCardPaymentProcessor {
...
}
When an enabled bean specializes another bean, the other bean is never instantiated or called by the container. Even if the other bean defines a producer or observer method, the method will never be called.
So why does specialization work, and what does it have to do with inheritance?
Since we’re informing the container that our alternative bean is meant
to stand in as a replacement for the default implementation, the
alternative implementation automatically inherits all qualifiers of the
default implementation. Thus, in our example,
MockCreditCardPaymentProcessor
inherits the qualifiers @Default
and
@CreditCard
.
Furthermore, if the default implementation declares a bean EL name using
@Named
, the name is inherited by the specializing alternative bean.