Adam Warski

7 Dec 2010

Improving autofactories/assisted inject

jee
dependency injection
java

In my two previous posts I wrote about some problems with DI and a solution to part of those problems: assisted inject (as known in Guice)/autofactories (my implementation for CDI). Some problems remained however; for example in the bean constructor, the dependencies (or the environment) are mixed with the data obtained from the factory. That is also why the @FactoryParameter annotation is required.

This shortcoming can be quite easily overcome, by using field injection for dependencies, and constructors for data from the factory. How does it work? And what about testing? Read on :).

The problem

The main goal is to be able to define beans, in which dependency injection works as usual and which can be constructed on-demand with user-supplied data. Something more object-oriented than creating stateless beans where the user-supplied data is passed in method parameters, and something with less boilerplate than implementing factories.

Just to remind, as an example throughout the posts I am using a ProductShipper bean, which needs some outside dependencies (“services”, for example a PriceCalculatorService), and must be provided with a Product object.

The solution

The base interface remains the same:

public interface ProductShipper {
     void ship(User target);

     interface Factory {
          ProductShipper create(Product product);
     }
}

Again just to remind, the biggest advantage of the nested factory interface is that it is clear what is needed to create a bean (here the shipper), and we spare some typing as we don’t need to write a long class name for the factory.

Now for the implementation. Before both outside dependencies and factory parameters were passed in the constructor. Now, we’ll use field injection, making a clear separation:

@CreatedWith(ProductShipper.Factory.class)
public class ProductShipperImpl implements ProductShipper {
     @Inject
     private PriceCalculatorService priceCalculator;

     @Inject
     private TransportService transport;

     private final Product product;

     public ProductShipperImpl(Product product) {
          this.product = product;
     }

     // implement shipTo(User)
}

Looks quite simple. The only thing we needed to do is to specify the factory for the bean (@CreatedWith) and provide a constructor matching the parameters of the factory. Hence, one problem remains: there’s no compile-time checking (only deployment-time), if the arguments lists match. So there’s still room for improvement! :)

Usage

To use the autofactory, you simply have to inject the factory bean. The important thing is that you don’t have to implement the factory bean by hand, it is auto-generated. For example:

public class Test {
     @Inject
     private ProductShipper.Factory productShipperFactory;

     public void test() {
          ProductShipper shipper = productShipperFactory
               .create(new Product("butter"));

          shipper.shipTo(new User("me"));
     }
}

Testing

Many would argue that using field injection instead of constructor injection makes the bean much harder to test. But we can improve that quite easily, thanks to the CDIInjector helper class, from <a href="https://github.com/softwaremill/softwaremill-common/tree/master/softwaremill-util">softwaremill-util</a>. Here’s how we can test our bean:

import static pl.softwaremill.common.util.CDIInjector.*;

public class ProductShipperTest {
     @Test
     public void testShipping() {
          Product product = ...;
          ProductShipper shipper = new ProductShipperImpl(product)

          // Here's the important part: "dependency injection"
          into(shipper).inject(mockPriceCalculator, mockService);

          // Test goes on ...
     }
}

So instead of setting the fields explicitly via reflection (where we would have to write the field names), we just need to provide the target of injection (<strong>into(shipper)</strong>), and the objects that we want to be injected (<strong>.inject(...)</strong>). We can also inject objects with qualifiers.

As always the code is available on github, as part of the softwaremill-common project (<a href="https://github.com/softwaremill/softwaremill-common/tree/master/softwaremill-cdi">softwaremill-cdi</a> release 9 and <a href="https://github.com/softwaremill/softwaremill-common/tree/master/softwaremill-util">softwaremill-util</a> release 13). The autofactories implementation is a portable extension (supports both styles: constructor and field/setter injection for dependencies), so all you need to do is to include the jar in your deployment.

Adam

comments powered by Disqus

Any questions?

Can’t find the answer you’re looking for?