choosing CDI @Alternatives at runtime
One of the coolest features of JSR-299 are @Alternatives, letting you choose concrete implementations by simply specifying them in your beans.xml, either each one seperately or grouped in @Alternative @Stereotypes. Since we have to choose @Alternatives according to the environment the application is deployed to (development, testing, staging, production...) I was looking for a way to configure the CDI-container whithout building seperate wars for each one.
I just could’nt believe I was the only person having this problem I searched the web for a solution, even made threads at the Weld Users Forum and at stackoverflow, but nobody was able to help me - so after sinking way to much time into the JSR-299 spec and weld source code I came up with this:
Goal: we want to choose different sets of @Alternative
s depending on the environment our war file is deployed to. First thing we do is create an @Alternative @Stereotype
to
hold our meta-information and also in order to just add one type to the beans.xml
<alternatives>
section instead of adding each @Alternative
seperately:
@Alternative @Stereotype @Retention(RUNTIME) @Target(TYPE) public @interface EnvironmentAlternative { EnvironmentType[] value(); }
EnvironmentType
is just an enum
detailing the different - wait for it… environment types:
public enum EnvironmentType { DEVELOPMENT, TESTING, STAGING, PRODUCTION; }
We need to make sure to have a single @Default
implementation for each interface, CDI will abort during deployment otherwise. All other implementations get annotated with our nnw
stereotype:
@Default public class MockupFoo implements Foo { ... } @EnvironmentAlternative({STAGING, PRODUCTION}) public class FooImpl implements Foo { ... }
As EnvironmentAlternative
is an alternative stereotype MockupFoo
is now choosen by the CDI container for all injection points. After we add the following lines to our beans.xml
FooImpl
is choosen instead:
<alternatives> <stereotype>com.example.EnvironmentAlternative</stereotype> </alternatives>
Not exactly what we wanted, but there’s something we can do about that: ever heard of CDI extensions? They are a way to extend CDI (duh) by observing CDI lifecycle events. Writing one
is quite easy, just extend the Extension
interface:
The extension itself is not very complicated: the processAnotated
method gets called for each annotated type the container processes, and if it is annotated as @EnvironmentAlternative
AND the current environemnt is NOT in the specified environments we call the event’s veto()
method, this prevents the type from being processed any further. Easy! The only thing left
to do is create a file called javax.enterprise.inject.spi.Extension
in your app’s META-INF/services
directory containing a single line: our extension’s fully qualified classname.
In a real application of course instead of hardcoding the currentEnv
field you’d have to decide how to obtain the current environment type, I just use a system property in our different glassfish
installations that I create with
nsn@nsn: ~> asadmin create-system-properties ENVIRONMENT=DEVELOPMENT
and then simply obtain via
String propertyValue = System.getProperty("ENVIRONMENT"); currentEnv = EnvironmentType.valueOf(propertyValue);
In our current project my team and I use this method to be able to build our application once, then deploy it to different environments and have it behave differently there. The only
problem I wasn’t able to solve with this technique is choosing which @Decorators
are enabled, as if I just veto()
a @Decorator
that is explicitly enabled in the beans.xml the container
complains about not being able to find that class. If anybody comes up with a solution to this please contact me somehow, for now our @Decorator
s themselves are responsible for when they
are active and when not.
comments powered by Disqus