Replacing Application Behavior at Build-Tme using CDI

Published in Java on 10-Aug-2014

One of the things I am continually amazed by is the simplicity of doing complex things in JEE 6. The Enterprise Java framework has always been about trying to abstract away and automate complex details (Transactions, Persistence, etc.) to allow an application developer to focus on what really matters - the business logic of their application. Never mind that for a long time the Enterprise Java framework was a miserable failure at this goal - it seems that with JEE 6 they finally got it right.

The Context and Dependency Injection (CDI) portion of the JEE 6 framework is one of the most useful pieces. Dependency injection is nothing new, and even JEE 5 had some limited injection abilities, but CDI makes just about anything injectable, just about anywhere. One very useful application of this technology that I have found lately is controlling the behavior of an application through the build-time injection of concrete implementations.

I am presently working on a system that has two distinct deployment modes. Much of the application's behavior changes based on which deployment mode is in use, but that deployment mode never changes - once a system is built for a specific mode it is constant. How could these different behaviors be managed?

When I first realized that many different concrete implementations of behavior would be needed, my first thought was to use CDI Producers (something like factories). Those producers could check a flag set somewhere, perhaps in the Application Scope, and then return the appropriate implementation of the relevant interface. However, this approach seems to needlessly complicate the code. Many producers would be needed, increasing the number of classes in the system, and conditional logic would have to be added to each producer to select between the various implementations. Not only that, but all the classes that would not be used for a given mode would still exist on each system, accomplishing nothing but taking up room on disk.

I decided a better strategy was in order. One of the useful features of CDI is that all CDI beans automatically are annotated with @Default, unless another qualifier is specified manually. This means that if you create an interface, a concrete implementation of that interface and then use injection to get the dependency, you will automatically get a reference to the concrete implementation without any additional work.

public interface SomeInterface {
    void doSomething();
}

public class SomeImplementation implements SomeInterface {
    public void doSomething() {
        //Some functionality
    }
}

public class SomeClassWithDependencies {
    @Inject
    private SomeInterface someInterface;

    //...
}

Of course things get complicated if you have multiple implementations of the interface on the classpath at any one time, but for the scenario I'm describing here, that isn't an issue.

Knowing this, we can put the interface for our functionality into a common API module. We can then create our two different implementations, each inside a different jar. When we wish to build our system for the first deployment strategy, we include the first implementation jar inside our EAR file. When we wish to build our system for the second deployment strategy, we include the second implementation jar inside our EAR file. If using a build tool like Maven, all this can be accomplished easily using build profiles.

In the particular system I am working on, the way in which system email is configured is handled like this. In the first deployment strategy, it is assumed that sendmail or postfix will be installed on the system and configured appropriately to send email. In this case, the Application Server is configured with a default mail session in the JNDI, configured to use localhost as the mail server. We want our implemetnation to return the javax.mail.Session object for this Application Server configured mail session. In the second deployment strategy, our application must query another subsystem on the box to fetch mail configuration properties, and create a javax.mail.Session object with the returned configuration properties.

A trivial illustration of this follows:

//An interface defined in an Maven API 
//module that is always included in our EAR file.
public interface MailSessionFetcher {
    Session getMailSession();
}

//A class that is responsible for sending emails, 
//always included in our EAR file.
public class MailSenderService {
    @Inject
    private MailSessionFetcher mailSessionFetcher;

    public void sendEmail() {
        final Session session = mailSessionFetcher.getMailSession();

        sendEmailUsingSession(session);
    }

    private void sendEmailUsingSession(final Session session) {
        //Code to send an email using the provided mail session
    }
}

//An implementation of the MailSessionFetcher
//that loads a mail session from the JNDI.
@ApplicationScoped
public class ApplicationServerMailSessionFetcher
        implements MailSessionFetcher {

    @Resource(mappedName = "java:/mail/default")
    private Session mailSession;

    @Overrice
    public Session getMailSession() {
        return mailSession;
    }
}

//An implemenation of the MailSessionFetcher that 
//loads mail properties from a subsystem and uses 
//those properties to configure a javax.mail.Session object
public class SubsystemMailSessionFetcher
        implements MailSessionFetcher {

    @Override
    public Session getMailSession() {
        return Session.getInstance(
            getMailPropertiesFromSubsystem();
	);
    }

    private Properties getMailPropertiesFromSubsystem() {
        //Functionality to query the subsystem
        //and load mail configuration properties
    }
}

Both of these implementations implicitly have an @Default annotation. If we deployed our application EAR to our application server with both of these classes in the EAR, the application would fail to deploy citing a problem with an ambiguous dependency. The Application Server doesn't know which of these implementations to inject into the MailSenderService. But if only one of these implementations exist in the EAR it will be injected and used.

If we create two Maven profiles, one which includes only the JAR containing the first implementation in our application EAR file and the other which includes only the JAR containing the second implementation, we can control the functionality of our application at build time. No logic is required in the code to decide between the two implementations and when we are distributing a system that uses one of these implementation, the other implemetnation is not sitting there collecting dust.

While this isn't a scenario that most programmers run into every day, there are many times where it can be particularly useful. In addition to the scenario described above, another useful application of this approach is for scenarios where you wish to include debug, backdoor-style code in your application, to make testing and development easier. Obviously you would only want code like this included while developing your software as it could create security vulnerabilities if accidentally released into production. In that scenario, a no-op implementation could be included in your EAR file using your default Maven build profile and then the implementation of your backdoor code could be included in your EAR file in a special Maven build profile. This means that you have to go to extra lengths to include the backdoor code (applying the special build profile) and as such it should not get included in your EAR by mistake.

About the Author

dan.jpg

Daniel Morton is a Software Developer with Shopify Plus in Waterloo, Ontario and the co-owner of Switch Case Technologies, a software development and consulting company. Daniel specializes in Enterprise Java Development and has worked and consulted in a variety of fields including WAN Optimization, Healthcare, Telematics, Media Publishing, and the Payment Card Industry.