Weld CDI: User Injected Functionality
Here is the scenario: Suppose you are creating a library that will aggregate data from a system and send that information to standard out. A user may want to use your library to aggregate the same information but wants to send the data elsewhere (eg, a db, a file, etc).  How do you provide these feature to you library without the user having to explicitly create your object and pass in a writer? (Similar to creating an extensible application or plug-in.) You could use Java’s Service Provider but then you would have to programmatically exclude default behavior when users implement their own.
Recently, I’ve been working on an creating an annotation library. In order to create an annotation library in Java, you have to extend an AbstractProcessor
and declare that class as a javax.annotation.processing.Processor
in META-INF/services
. Java will pick this class up automatically, so I need a way inject a users implementation class without changing the code and I want to disable my default implementation when this occurs. In comes Dependency Injection (DI) in the form of JEE’s CDI service.
The following is an example of how to replace one bean implementation with another using CDI. I’m using the JEE reference implementation of CDI, Weld, in an SE fashion. So there is no need to run in a container. The example uses Alternatives to replace a default implementation with a user created implementation. You can find the working example on github.
https://github.com/Scuilion/weldit
The first thing is initializing Weld. Weld requires the base beans.xml
file to be in the META-INF
folder and because I’m not using an EE container, the container has to be generated manually (Note how the container is created and destroyed before the injected class is used.)
public class Producer {
@Inject
Writer writer;
public someLibraryMethod() {
Weld weld = new Weld();
WeldContainer container = weld.initialize();
writer = container.instance().select(Writer.class).get();
weld.shutdown();Â
writer.process();
}
}
Writer
is the interface that you and the consumer of you library will implement. You’re library will come with a default implementation (WriterImpl.java
). If the consumer does not create their own implementation then Weld will load up this default.
public class WriterImpl implements Writer {
@Override
public void process() {
System.out.println("in default writer implementation");
}
}
And here is and example users implementation. All we have to do is use the @Alternative
annotation to tell weld that we want to use this class as oppose to the default class to be injected.
@Alternative
public class ReplacementWriterImpl implements Writer {
@Override
public void process() {
System.out.println("\***\***\***\***\***\***\***\***");
System.out.println("in alternative");
System.out.println("\***\***\***\***\***\***\***\***");
}
}
Notice that the only thing different is some extra print statements. Let’s see how this works when running. I’ve set up test for the base
and the consumer
under com.BaseTest
and com.ConsumerTest
, respectively. If I run the base
test, again with only the default implementation I get the following.
kmb-us-master109:weldit kevin.oneal$ ./gradlew :base:test
:base:compileJava
:base:processResources
:base:classes
:base:compileTestJava
:base:processTestResources
:base:testClasses
:base:test
com.BaseTest > testSomeLibraryMethod STANDARD_OUT
in default writer implementation
BUILD SUCCESSFUL
Total time: 4.089 secs
And when we run the consumer
test, we get the following.
kmb-us-master109:weldit kevin.oneal$ ./gradlew :consumer:test
:base:compileJava UP-TO-DATE
:base:processResources UP-TO-DATE
:base:classes UP-TO-DATE
:base:jar
:consumer:compileJava
:consumer:processResources
:consumer:classes
:consumer:compileTestJava
:consumer:processTestResources UP-TO-DATE
:consumer:testClasses
:consumer:test
com.ConsumerTest > testSomeLibraryMethod STANDARD_OUT
in consumer
before called to producer
\***\***\***\***\***\***\***\***
in alternative
\***\***\***\***\***\***\***\***
after called to producer
BUILD SUCCESSFUL
Total time: 3.971 secs
You can get this project from github under the tag v0.1
.
Ultimately, the difference between using the Service Provider or Weld’s interpretation of CDI is that with DI I can eliminate the very trivial default implementation. Although I haven’t tried it, Weld is suppose to allow you to mimic the feature of Service Providers where Java will pick up all implementation of an interface/abstract, just by changing the @Inject
variable to take a list.