- 12 minutes read

Let's bust a myth. There are very good reasons to use CDI or Spring, but dependency injection isn't one of them. There are a lot of alternatives. And I'm not talking about Google Guice, although that's a very fine DI framework indeed. Why don't we write our own DI framework? Can't be difficult, can it?

It's true: today's open source libraries make writing your own DI framework incredibly simple. But even without such a library writing a DI framework isn't difficult (at least if you're ready to learn something about class loaders). I'll develop a sketchy DI framework in this article. In theory, you could finish my work to implement a full-blown DI container and put it into production. Even if you don't, you'll understand how CDI and Spring work after reading this article.

But first I'd like to present you an alternative you're probably not aware of.

JSF

It always pays to have a look into our toolbox before starting to code. Do you happen to use JSF? Among other things, JSF is a light-weight dependency framework.

Most people who look at the multi-megabyte bundles of Spring and CDI abandon all hope. Whow, so much code, must have been difficult to write! But at the end of the day, a dependency injection framework doesn't do much: it provides a program with certain objects. Your program doesn't create the objects itself. Instead, it asks the DI framework to execute the dreaded "new" keyword.

The ugly truth (that hardly anybody admits) is that doing so almost always adds extra complexity to your program. You shouldn't use a DI framework just for the fun of it. There's nothing wrong with the "new" key word, especially in small programs. The extra complexity pays off because it allows you to separate the API and the implementation into different projects (or jar files). This, in turn, allows you do replace an implementation by an improved version. Or to replace the real-world implementation by a mock implementation that's used for testing purposes. More generally speaking, DI makes it easier to decouple modules from each other. Used wisely, DI rewards you with a clean code structure.

The other advantage of using a DI framework is that the DI framework can manage the object's life cycle. In other words, it take care about the object's scope. The scopes of Spring more or less match the scopes of JSF. There's a application scope (aka "singleton scope" in Spring), a session scope, a request scope and the prototype scope (called "none scope" in JSF).

This close match makes it possible to replace JSF managed beans by Spring Beans (see my article How to integration JSF 2 and Spring nicely). Today I'm interested in doing things the other way round. Granted: JSF offers less features than a full-blown DI framework, but if you're only interested in the core functionality JSF works every bit as well. All you have to do is to replace @Component and @Service by @ManagedBean, replace the Spring scope annotations by their JSF counterparts and replace @Autowired by @ManagedProperty.

But... what about the advanced Spring features?

Why, you loose them. This article is not about Spring or CDI. It's about dependency injection. I want to show you dependency injection is nothing to stand in awe of. I wrote this article because I observed many programs don't benefit from Spring or CDI. These are mighty tools that pay off after spending a lot of time with them. The occasional smartphone app is better off without such a feature monster.

Back to the missing features. There's a plenty of Spring features JSF doesn't offer. For instance, there are no producer methods in JSF. You can't parameterize your JSF beans in an XML file. JSF doesn't find the implementation to an interface. You can't switch between two different implementations with a simple switch (such as modifying the profile in the web.xml in the Spring case). So what? Do you really need these features? Chances are you won't miss them. If so, consider one of the simpler alternatives.

What you can do is you can decouple modules. JSF finds its beans automatically by scanning the entire class path for a bean with a matching name. It doesn't matter where the bean is. It can be situated in another jar file, which may not even be in the compiler's class path.

By the way, there's a even way to switch between profiles in JSF. Put each of the two implementations into a separate jar file, and you can switch by simply deploying only one the two jar files. In most cases this will do the trick just as well. Plus, you'll never deploy a mock implementation in production.

Factories

I promised you code. Let's start things simple. For that reason let's ignore scoping for a minute. We'll come back to that later. What is the most simple approach to dependency injection?

I claimed above the basic job of a DI framework is to ban the dreaded "new" keyword from your programs. You leave it to the DI framework to create the objects your program needs. The simplest way to (re-)factor the "new" keyword out of your program is to use a factory. Funny thing is, the standard Java SE API of both Spring and CDI looks suspiciously like a factory. Have a look at line 17 of the example on the front page of the Spring documentation:

@Configuration @ComponentScan public class Application { @Bean MessageService mockMessageService() { return new MessageService() { public String getMessage() { return "Hello World!"; } }; } public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(Application.class); MessagePrinter printer = context.getBean(MessagePrinter.class); printer.printMessage(); } }

Replace the ApplicationContext by a factory of your own, and you've written a very primitive dependency framework:

public class DIFactory { public getBean(Class theInterface) { if (theInterface == MessagePrinter.class) { return new MessageService(); } } } public class MessageService implements MessagePrinter { public String getMessage() { return "Hello World!"; } } public class Application { public static void main(String[] args) { DIFactory context = new DIFactory(); MessagePrinter printer = DIFactory.getBean(MessagePrinter.class); printer.printMessage(); } }

Comparing the two approaches, you probably notice we've lost a nice Spring feature. We can't use a producer method. Our factory doesn't scan for methods annotated by @Bean. But as I said before, that's not the point. I don't deny Spring (or CDI, for that matter) is a great framework. I just want to give you a hint how Spring is implemented, and to show you there's nothing magic about it.

Another close look reveals we've lost decoupling of modules on the compiler level. Oops. It's not easy to exchange one factory for another. Replacing the implementation without recompiling requires you to put the implementations into a jar file. But we'd like to get rid of the hard dependency at compile time. That's not exactly the idea of dependency injection. Maybe factories aren't the right tool for the job.

Naming conventions

Let's look for a more flexible solution, a solution allowing you to separate the implementation from the interface in different jar files. Java has this nice reflection API to access classes you only know by name. A couple of years ago "Convention Over Configuration" became fashionable. So why don't we take the interface's class name, add an "Impl" and use it as class name?

public class DIFactory { public getBean(Class theInterface) { try { Class clazz = Class.forName(theInterface.getName+"Impl"); return clazz.newInstance(); } catch (ReflectiveOperationException e) { // LOGGER.error("Couldn't find or instantiate an implementation...", e); return null; // better throw an error } } }

Adding scope awareness

Do you remember how difficult it was to learn the differences between scopes? They cover several pages in the average tutorial. That's one of the cases which are easily implemented but difficult to explain. Usually scopes are simply hash tables. Consider a web application (based on a servlet):

  • The session scope is a hash map stored in the list of session parameters.
  • Likewise, the request scope is a hash map stored in the request object.
  • The application scope can simply be a hash map stored in a static variable.

So in a JSF application a very simple way to implement scope aware dependency injection looks like so:

public class MessageService implements MessagePrinter, HasScope { public String getScope() { return "session"; } public String getMessage() { return "Hello World!"; } } public class DIFactory { public synchronized getBean(Class theInterface) { // To run the code in a servlet you have to pass the session object to the DIFactory. // In JSF we can simply access the session as a ThreadLocal object: Map sessionScope=FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("di-sessionscope"); if (sessionScope.contains(theInterface)) { return sessionScope.get(theInterface); } try { Class clazz = Class.forName(theInterface.getName+"Impl"); Object bean = clazz.newInstance(); if ("session".equals(((HasScope)bean).getScope()) { sessionScope.put(theInterface, bean); } return bean; } catch (ReflectiveOperationException e) { // LOGGER.error("Couldn't find or instantiate an implementation...", e); return null; // better throw an error } } }

To run the code in a servlet you have to pass the session object to the DIFactory. You can implement request scope by using the HTTP request object. Application scope is even simpler: store it in a static hash table.

I kept things sketchy to show the idea more clearly. I omitted the initialization of the DI-sessionscope object in the session. The "synchronized" bit is a bit odd (it works, but it slows down the entire application). I only implemented two scopes ("session" and "prototype" aka "none scope"). Most likely the code snippet is buggy. Plus, in the age of fancy annotations the getScope() method looks a bit odd. I preferred to keep the example short and simple. Of course it's just a matter of a few lines to replace the getScope() method by an annotation.

Parsing the class path

Talking of annotations... it's surprisingly simple to replace the factory by an annotation-centric approach. For some reason the Java API designers forgot to implement a powerful Package class, but the framework designers found ways to work around the limitations of the API. The basic idea is to ask the current ClassLoader which class paths are active, to load every file in the class path and to analyze it. With a little effort you can grab the implementation from Weld or from Ronald Muller's AnnotationDetector.

Today I've learned there's an interesting alternative: there's an open source library called Reflections that already did the hard work for you. Add the following dependency to your Maven file:

org.reflections reflections 0.9.9-RC1 compile

Now getting a the list of classes bearing a certain annotation is a matter of two simple lines:

Reflections reflections = new Reflections(""); Set> types = reflections.getTypesAnnotatedWith(Service.class);

Using this list you can create a hash table mapping interfaces to the implementation classes.

public class DIFactory { private static Map diMap = new HashMap<>(); private static Map applicationScope = new HashMap<>(); static { Reflections reflections = new Reflections(""); Set> types = reflections.getTypesAnnotatedWith(Service.class); for (Class implementationClass : types) { for (Class iface : implementationClass.getInterfaces()) { diMap.put(iface, implementationClass); } } } public Object getBean(Class interfaceClass) { Class implementationClass = diMap.get(interfaceClass); if (applicationScope.containsKey(interfaceClass)) { return applicationScope.get(implementationClass); } synchronized (applicationScope) { Object service = implementationClass.newInstance(); applicationScope.put(implementationClass, service); return service; } } }

Sometimes Beans can register themselves

Sometimes there's a simpler alternative to using the Reflections library. Most developers use a library like Hibernate or JSF, which scans the entire classpath for annotations. To do so, many libraries load the class into memory. In this case you don't have to scan the classpath yourself. It suffices to add a static initializer to your beans and services. The static initalizer is executed when the class is loaded into memory, so you can use it to register your class with our DIFactory:

public class MyService { static { DIFactory.register(this); } // business code follows here }

The drawback of this approach is that it's fragile. It depends on the other libraries.

What's missing?

Both Spring and CDI populate variables within beans automatically if they are annotated with @Autowired or @Inject, respectively. Our algorithm doesn't do that yet. I guess you've got the gist by now: either you replace @Autowired by a call to the DIFactory, or you add some code to the DIFactory that uses reflection to find the attributes that have to be populated. Either way it's not difficult. Just a bit tedious. That's the real value of CDI and Spring: they've implemented the boring parts of DI, and they added a lot of additional features along the way.

Google Guice

A light-weight alternative to CDI or Spring is Google Guice, a DI framework I examined a couple of years ago. It's a full-blown dependency injection framework that's a lot smaller than Spring or CDI. It doesn't bring a giant ecosystem with it. It focuses on Dependency Injection and a little AOP, but nothing else. That makes it easy to learn, so it's worth a look (unless you need the JavaEE or Spring ecosystem).

Wrapping it up

These days everybody seems to use Spring and CDI. Many developers believe they can't use dependency injection without the help of such a framework. Well, that's an urban myth, and I showed you how to replace a full-blown DI framework by an implementation of your own. Mind you: I don't really recommend to do so. I just wanted to show you Dependency Injection isn't quite as difficult as people tend to believe. Sometimes there's a simpler solution that does the trick just as well.


Comments