I've written a couple of posts about using @Alternative to achieve deployment-time polymorphism. What I've never really talked about is its role in CDI modularity.
Modularity and alternatives
From our point of view, support for modularity
in dependency management means that injection points of the same type, in different modules, can resolve to different beans. For example, suppose I have an interface Supplier, with two implementations. Let's say they are beans like BookSupplier and CameraSupplier, but they could easily be something more exotic. Now, in one module, bookshop.war, of my application, I have a BookShop:
class BookShop {
@Inject
BookShop(Supplier supplier) { ... }
...
}
In another module, camerashop.war, I have a CameraShop:
class CameraShop {
@Inject
CameraShop(Supplier supplier) { ... }
...
}
Now, how does the container tell that the Supplier in BookShop is a BookSupplier, but the Supplier in CameraShop is a CameraSupplier?
Well, there's two possibilities:
BookSupplier and CameraSupplier are deployed in different modules
Suppose that BookSupplier and CameraSupplier are deployed in separate modules, books.jar and cameras.jar respectively. Then the solution is easy. Just declare that bookshop.war depends on books.jar and that camerashop.war has a dependency to cameras.jar, and we're done. That's enough information for CDI to know what to inject where.
So bookshop.war would contain the following line in MANIFEST.MF:
Class-Path: books.jar
Meanwhile, camerashop.war would contain this line in its MANIFEST.MF:
Class-Path: cameras.jar
BookSupplier and CameraSupplier are deployed in the same module
But what if BookSupplier and CameraSupplier are both deployed in suppliers.jar? Well, there's a solution in this case, too.
First we need to add the @Alternative annotation to BookSupplier and CameraSupplier. @Alternative tells CDI not to inject a bean into a module that does not explicitly declare its dependency to the bean.
Next, we need to declare the appropriate alternative in each module. In bookshop.war, we specify:
<beans>
<alternatives>
<class>org.mydomain.supplier.BookSupplier</class>
</alternatives>
</beans>
In camerashop.war, we specify:
<beans>
<alternatives>
<class>org.mydomain.supplier.CameraSupplier</class>
</alternatives>
</beans>
And now each module has it's own, private, implementation of Supplier.
What if BookShop and CameraShop are deployed in the same module?
I suppose, at this point, you're wondering what happens if the clients, BookShop and CameraShop are deployed in the same module. Well, in this case we need to look for another solution. CDI doesn't support more granular alternative activation. There are no bean-level or package-level alternatives. (At least not in this release of CDI.)
Instead we need to add a qualifier, for example:
class BookShop {
@Inject
BookShop(@Supplies(Book.class) Supplier supplier) { ... }
...
}
or, more likely, add a generic type parameter to Supplier:
class BookShop {
@Inject
BookShop(Supplier<Book> supplier) { ... }
...
}
Unified EL
The example we've worked with involves Java classes using injection to obtain their dependencies. But what about JSP or JSF pages, which use Unified EL expressions to interact with beans. Well, happily, exactly the same rules apply during EL name resolution.
Modularity beyond Java EE
I've given examples from the Java EE environment. In this environment, a module
is a Java EE module like a war, EJB jar or rar, or a library jar. But what about other module architectures like OSGi or a future Java language-level module system?
Well, the CDI spec is slightly future-proof in this respect. This functionality is defined for an abstract notion of a module in an abstract module architecture
. So it's more or less well-defined what a CDI implementation should do in an OSGi architecture, if it wanted to support OSGi.