Fisheye and Crucible don’t use any IoC at present, and as part of the process of choosing one we decided to spike using Guice as Crucible’s IoC container.
We wanted to learn about Guice because it has some interesting differences compared to Spring:
- Configuration is in Java, not XML. This gives better compile time type checking, better refactoring support, and a more expressive configuration language.
- Injection is done via annotations instead of named attributes (you can do this with Spring 2 as well). This has the disadvantage that your concrete classes depend on Guice, but the advantage that the IoC container knows exactly which injections are expected, so that not providing a binding becomes an error at start up rather than a later NullPointerException.
The first step was to bootstrap the container, which I did by installing a ServletContextListener which sets up Guice when the context was initialized:
The static init method does three things:
- Create an Injector from the CrucibleModule class.
- Make that Injector available as a static variable for those cases when we have to manually inject dependencies.
- Configure the frameworks used by Crucible to allow Guice to handle object creation.
The basic challenge when you fit an IoC container into an application is to allow the container to inject dependencies into objects as they are created. This means taking over object creation for as many parts of your frameworks as you can.
For the purposes of the spike we want to remove the need for a servlet filter which makes the current HttpServletRequest and HttpServletResponse available as static fields.
In other words, instead of writing CrucibleFilter.getRequest() we want to say @Inject HttpServletRequest request; as a field in a class, or @Inject MyClassConstructor(..., HttpServletRequest request, ...) to provide the value as an argument to a constructor.
To do this either the object must be constructed by Guice, or for the first case we can cheat and use injector.injectMembers(existingObject) which of course doesn’t work for constructor injection. In addition, the lifecycle of the object must match the lifecycle of the object being injected into it – injecting a request into an xwork Interceptor doesn’t work, as the interceptor has a multi-request lifetime. Xwork Actions are fine, as they are created for each request.
We can address the scope mismatch by injecting a Provider instead of an actual value:
and then calling requestProvider.get() when we need the object it provides. I don’t understand how the type parameter is available to Guice at runtime, but it is, as this code works.
The cookbook recipe for what I needed to do is:
- Add the context listener to initialise Guice to web.xml
- Add the GuiceFilter to web.xml, to make request and response available.
- Make the CrucibleModule install Guice’s ServletModule, which binds the request and response into the module.
- Create all actions (and other Xwork objects) via Guice:
- Bind a new ObjectFactory implementation in the CrucibleModule: bind(ObjectFactory.class).to(GuiceObjectFactory.class).in(Scopes.SINGLETON) this class uses Guice for object creation by overriding the buildBean method:
- Set this ObjectFactory into Xwork’s ObjectFactory singleton when we initialise Guice: ObjectFactory.setObjectFactory(injector.getInstance(ObjectFactory.class))
- Change BaseAction to take an HttpServletRequest as a constructor argument (we can’t use field injection because BaseAction uses the request in its constructor)
- Change every subclass of BaseAction to have the appropriate constructor, with an @Inject annotation.
- As described above, change Interceptors which need the request to have a Provider injected.