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.