02 mayo 2011

Using Spring dependency injection without XML

This is my second post in English, which is not my first language, so if you find errors let me know :)
Having said that, let’s go with the blog post…

It’s difficult to define what the Spring Framework provides, because is not a framework but a collection of them. The included frameworks covers pretty much everything from dependency injection (DI) to the creation of web applications. All of them designed to work seamlessly with the DI framework, and in particular with the XML way of configuring DI.

By using dependency injection you don’t need to modify your model to use Spring: you depend only on the DI framework to get the root objects of your application. At “lower levels” you can use some abstractions provided by the Spring Framework like JdbcTemplate or JmsTemplate, but you can use them without the DI framework too: they are part of the “Spring framework collection”, but they can work as independent frameworks.

Sadly in my experience, it seems that most of the developers are not aware of that: a lot of projects are too coupled with the Spring XMLs (which is only one convenient way of configuring DI), to the point that there are also dependencies to the “application-context.xml” in the unit tests. This is really bad. Spring XML is easy to write, but is a pain to maintain: it lacks abstraction mechanisms, you can’t debug it, is difficult to refactor and to find where the things are.

An alternative is to use Google Guice for the dependency injection part: It contains a nice API to define instance dependencies in code. You can use it with other Spring things, but I never feel comfortable to do that, because I think that mixing both frameworks could be a little bit confusing.

Spring DI can be configured by code, but to do it you have to pass some barriers:

  • All the documentation refers to the XML configuration.
  • The API to do the configuration directly in Java is awful.

 

Understanding the Spring API

The base interface to get instances from the DI injection framework is BeanFactory. The “Bean” prefix is there for historical reasons, but not be confused by it: your object classes doesn’t need to comply with the JavaBeans specification.

But to use a BeanFactory instance you need to configure it in some way, that’s why exists other interface called BeanDefinitionRegistry. This interface defines methods to register bean definitions (instances of BeanDefinition) that indicates how to create an instance.

To summarize:

  • BeanFactory: provides bean instances by name (getBean)
  • BeanDefintionRegistry: associates bean names with bean definitions (registerBeanDefinition).
  • BeanDefinition: provides information on how to create an object instance.

    Design side note: To me Spring is unnecessary complex in this part. The BeanDefinition provides information but it doesn’t do the instantiation, it means that each BeanFactory has to do that work (see AbstractBeanFactory.doGetBean). I think that if Spring delegates the instantiation to the definition, a lot of things could be simplified and the API becomes easier to use directly from Java.

If you used Spring before, you probably heard the name “ApplicationContext”. This is because the usual ways to get an implementation of BeanFactory is by using a ClassPathXmlApplicationContext or a XmlWebApplicationContext. That classes also implements other interfaces (i.e. ApplicationContext) that allows to plug-in lifecycle observers or enumerate beans (I assume that those functionality is required by other more “advanced” features provided by the framework).

 

Creating a BeanFactory instance for testing purposes

Before explaining how to do that… I want to give you an advice:

If you need to pass a Spring “ApplicationContext” or a “BeanFactory” in a lot of tests, you need to review your design and/or the way that you are testing. Maybe you are putting direct dependencies with the Spring context when all you need is to pass the required instances directly or maybe you are instantiating too much just to create an unit test.

After that short advice, here are some small code examples:

You can define beans by code, sadly the API is really ugly: BeanDefinitionBuilder provides you with methods to define beans (Ey, Spring guys! A good internal DSL to do this will be nice in the next release of Spring):

final StaticApplicationContext context = new StaticApplicationContext();
final BeanDefinitionBuilder builder =
       
BeanDefinitionBuilder.genericBeanDefinition(MockDataSource.class);
context.registerBeanDefinition("dataSource", builder.getBeanDefinition());

new MyApplication(context).run(); // MyApplication(BeanFactory context)

Using StaticListableBeanFactory you can pass instances, instead of using bean definitions (really useful to do tests):

final StaticListableBeanFactory context = new StaticListableBeanFactory();
context.addBean("dataSource", new MockDataSource());

new MyApplication(context).run();

By using a GenericApplicationContext you can combine XML from different sources with beans defined in code, for example:

GenericApplicationContext context = new GenericApplicationContext();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(context);
// this xml define the contentManager bean
reader.loadBeanDefinitions(new ClassPathResource("spring-config.xml"));

context.registerBeanDefinition("dataSource",
        BeanDefinitionBuilder.genericBeanDefinition(MockDataSource.class)
                             .getBeanDefinition());
context.refresh();

// contentManager uses dataSource
System.out.println(context.getBean("contentManager"));

 

Ok that’s all for this time :)