In the next few articles of the "Plexus to Guice" series I will look at the modular design of our replacement Plexus container and show how you can configure a POB (Plain Old Bean) from Guice with a simple code example. In the first article of this series, Jason discussed the need to move to a more widely used and support container, and the reasons why we chose to standardize on Guice. As we migrate more Plexus-based applications (such as Maven) to Guice, we still to maintain backward-compatibility for all of the plugins and extensions which were developed using Plexus. In this post, I start to discuss the scope and initial efforts to create something we're calling the Guice/Plexus "shim". It is a library, a "container" that was developed to allow existing Plexus components to use Guice under the hood without any modification.
Scope, Architecture, and Conventions
Our goal is to create a swap-in replacement for Plexus built on top of Guice. Ideally this should be done without changing the core Guice code, but if this is not possible then any fixes or new functionality should be written up and reported on the Guice issues page. While there is no guarantee that these changes will make it into an official Guice release, improvements that benefit a wider audience should have a better chance of making it into Guice.
Sonatype currently maintains a patched build of Guice trunk with the following major patches:
- Provide access to Guice's own internal TypeConverters
- BytecodeGen and related AOP / bridge classloader fixes
This build also contains some experimental changes which have not yet been written up because they are still being tested:
- ability to turn off validation error about using @Singleton on an interface instead of an implementation class
- ability to turn off creation of parent JIT bindings when using child injectors (simplified version of this issue)
Guice/Plexus Integration Module Architecture
The solution is separated into re-usable, pluggable modules that together provide a replacement for the existing Plexus container. There is also an artifact that combines these modules into a single JAR, as this would make it easier to swap between the old and new container. Each module has a specific responsibility and any dependencies between modules is kept at a minimum. The following is a list of the components or modules that comprise the Guice/Plexus integration project.
- guice-bean-inject
- Extends Guice to support customized injection of named properties (fields and setter methods).
- guice-bean-reflect
- Provides utility methods and support code for reflection, bean properties, and resource scanning.
- guice-plexus-metadata
- Shared metadata interfaces, runtime implementations of Plexus annotations, and collection adapters.
- guice-plexus-scanners
- Annotation and XML scanners that provide metadata about components, requirements, and configuration.
- guice-plexus-converters
- Standard Plexus type conversion rules that can create instances from simple strings and XML markup.
- guice-plexus-locators
- Guice based registry that can locate components of a certain type, optionally ordered by name hints.
- guice-plexus-bindings
- Guice module that uses: scanners to find and bind Plexus component beans, converters to turn configurations into instances, and locators to find components based on requirements. This component also provides a simple lifecycle management API.
- guice-plexus-shim
- Creates an injector with Plexus bean support, adds Plexus lifecycles, and provides the container API.
A Brief introduction to Guice and JSR330
Guice is a Dependency Injection framework that injects constructors, methods, and fields annotated with @Inject. Guice recognizes both the JSR 330 and the original Guice form of this annotation.
Every injection point (constructor, method, or field) has a number of dependencies, each one represented by a key: the type to be injected plus an optional qualifier annotation that lets you choose between different implementations of the same type. For example:
public class Car { // Injectable constructor @Inject public Car(Engine engine) { ... } // Injectable field @Inject @Named("Corinthian Leather") private Seat seat; // Injectable package-private method @Inject void install(Windshield windshield, Trunk trunk) { ... } }
Has the following injection points and dependency keys:
Constructor ---> Key[ Engine ] Field "seat" ---> Key[ Seat, @Named( "Corinthian Leather" ) ] Method "install" ---> Key[ Windshield ], Key[ Trunk ]
The Guice injector maintains a set of bindings that map dependency keys to providers that supply instances of the key type. These providers may use different strategies to supply instances: per-lookup, singleton, per-conversation, even your own custom strategies.
When you configure Guice you are registering bindings from one key to another, or between keys and providers:
// Key[ Seat ] ---> Key[ FoamSeatImpl ] bind( Seat.class ).to( FoamSeatImpl.class ); // Key[ Seat, @Named( "Corinthian Leather" ) ] ---> Key[ LeatherSeatImpl ] bind( Seat.class ).annotatedWith( Names.named( "Corinthian Leather" ) ).to( LeatherSeatImpl.class ); // Key[ Engine ] ---> Key[ V8EngineImpl ] bind( Engine.class ).to( V8EngineImpl.class ); // Key[ V8EngineImpl ] ---> Provider[ V8EngineImpl, "singleton" ] bind( V8EngineImpl.class ).in( Singleton.class ); // Key[ Windshield ] ---> Provider[ Windshield, "custom" ] bind( Windshield.class ).toProvider( WindshieldProvider.class ); // Key[ Trunk ] ---> Provider[ TrunkImpl, "constant" ] bind( Trunk.class ).toInstance( new TrunkImpl() );
The injector can also create certain types of bindings on-demand when no such explicit binding already exists, such as:
// Key[ FoamSeatImpl ] ---> Provider[ FoamSeatImpl , "per-lookup" ] // Key[ LeatherSeatImpl] ---> Provider[ LeatherSeatImpl, "per-lookup" ]
The Key[...] and Provider[...] markup is pseudo-code |
So far so good, but what if you have classes that don't mark dependencies with @Inject? What if they're identified in XML or marked with custom annotations like @Requirement or @Configuration?
TypeListeners, MembersInjectors, and InjectionListeners
Since 2.0 Guice has provided a way to supplement the core injection process with your own custom injections. Here's what you need to do:
- Register your custom TypeListener, using a Matcher to filter the specific types you are interested in, or Matchers.any() if you want to process all types
- Guice will call your TypeListener once for each type that matches your chosen matcher, passing you a TypeEncounter
- You can then analyze the type to be injected, prepare any custom injection data, and use the TypeEncounter to register aMembersInjector or InjectionListener for this type. Because the TypeListener is called per-type, and not per-instance, you should try to do as much preparation work here as possible to avoid spending too much time in the MembersInjector or InjectionListener
- Guice will call your MembersInjector once for each instance it injects, after it has performed the core injection. You can then use the data prepared by your TypeListener to perform any custom injection
- Finally Guice will call your InjectionListener once for each instance it injects, after all injections are complete. This is where you can hook in management code, such as start and stop life-cycle support
You'll notice that Guice does not dictate how each type should be scanned or prepared for custom injection. While Guice does provide utility methods that can tell you which class members are annotated with @Inject, there's no general-purpose mechanism to scan class members looking for custom annotations. On the one hand this is good, because it avoids bloating the core JAR with code you might not need. But on the other hand each custom injection client could end up implementing similar scanning code again and again. The recommended approach is to put optional code like this in an extension library that people can use if they need to, rather than keep folding it into the core.
We're going to use custom injection to support Plexus components whose dependencies may be annotated or configured in XML. Plexus components are similar to Java beans in that they can have fields or setter methods, so rather than write a custom injector that's closely tied to Plexus let's see if we can develop an extension library for "beans" in general. That way we can build support for Plexus on top of it, and allow people to re-use it for other bean-style injections.
Written by Stuart McCulloch
Stuart is a Senior Software Engineer at Sonatype, working on Guice/OSGi integration, amongst other things.