DI and OO: Assisted Inject in CDI / Weld

1 Flares 1 Flares ×

My last post sparked quite a lot of interest – thanks for all the comments both on the blog and on dzone! Some of them rightly pointed out that the original post contains a mistake (*) (but luckily it didn’t impact the main point). Many other suggested using the factory pattern as a solution for the problem described. However, as I wrote in the post, this requires one to implement a factory, which is impractical and leads to quite a lot of boilerplate code.

Maciej Biłas pointed me however to a nice solution, that is implemented in Guice: Assisted Inject. The idea is that an implementation of the factory can be automatically generated by the container, basing on the factory’s interface and leveraging some additional metadata from annotations. As I’m using CDI/Weld for some of my projects, I decided to write a portable extension supporting this.

As always an example would be best to illustrate what it’s about; I called my implementation autofactories.

Suppose that, as in my last post, we have a ProductShipper interface. To create a shipper, we need a product, so we’ll create a factory which will have one method with one argument. We can write this as follows:

1
2
3
4
5
6
7
public interface ProductShipper {
     void ship(User target);
 
     interface Factory {
          ProductShipper create(Product product);
     }
}

By making the Factory a nested interface, we gain two things:

  • it is clear what is needed to create a shipper just by looking at the main interface (no need to look at a separate factory class)
  • we spare some typing as we don’t need to write another long class name (ProductShipperFactory). However, usages of the factory identify it exactly (ProductShipper.Factory – notice the dot)

Now the implementation; let’s say it requires two services, which we want to inject, apart from the Product object, which is obtained through the factory method. Using autofactories, we can write it like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@CreatedWith(ProductShipper.Factory.class)
public class ProductShipperImpl implement ProductShipper {
     private final PriceCalculatorService priceCalculator;
     private final TransportService transport;
 
     private final Product product;
 
     @Inject
     public ProductShipperImpl(@FactoryParameter Product product, 
          PriceCalculatorService priceCalculator, 
          TransportService transport) {
          // assign fields
     }
 
     // implement shipTo(User)
}

Now you can just inject the factory interface and call the create method on it, for example:

1
2
3
4
5
6
7
8
9
10
11
public class Test {
     @Inject
     private ProductShipper.Factory productShipperFactory;
 
     public void test() {
          ProductShipper shipper = productShipperFactory
               .create(new Product("butter"));
 
          shipper.shipTo(new User("me"));
     }
}

Note that we never wrote the actual implementation of ProductShipper.Factory. There are three annotations which are important here:

  • @CreatedWith specifies the factory interface, for which an implementation will be created. The interface should have only one method (later referred to as the factory method)
  • @FactoryParameter specifies that the annotated constructor parameter corresponds to a parameter of the same class in the factory method
  • @Inject specifies that other parameters should be injected from the context

Does assisted inject/autofactories solve the problems from my last blog? Only partly; there two big drawbacks of this solution:

  • The dependencies of a bean on the environment/context (injected from the container) and on data (passed from the factory method) are mixed, while they are different kinds of dependencies
  • No type safety: only at deployment time it is possible to verify that the parameters of the factory method match the constructor

The code is available on github, in the softwaremill-common project (softwaremill-cdi module). It is a portable extension, so you can use autofactories simply by including the jar in the classpath.

The jar is deployed to our public Maven repository, see the project’s README for the artifact data.

There is also a test using Arquillian which can be a good usage example.

Next step: combining object services (where the emphasis is on polymorphism and extension methods) with assisted inject/autofactories.

Adam

(*) The statement that you can only create one instance of an object is wrong. Injecting an Instance in CDI or a bean in the prototype scope in Spring, you can create on-demand instances. However all of them are constructed in the same way, using only dependencies from the container, so this doesn’t solve the original problem.

1 Flares Twitter 0 Facebook 1 Google+ 0 LinkedIn 0 Email -- 1 Flares ×