Using IoC Utils

This section presents a step-by-step example of IoC Utils usage. It shows how to create a service interface and implementation and how to bind them together. Then it demonstrates how to to set up the IoC registry and how to fetch the appropriate implementation for the service. Multiple implementations are also explained towards the end.

Defining the service interface

The first step is to define the service's interface. On this example, the service will have a single method named someMethod().

 1  public interface ISampleService {
 2  
 3      /**
 4       * Some method...
 5       */
 6      public void someMethod();
 7  }

Defining the service implementation

The service's implementation will be a class the that implements the previous interface. The implementation will be named SampleServiceImpl and someMethod will just print a message to System.out.

 1  public class SampleServiceImpl implements ISampleService {
 2  
 3      /**
 4       * @see ISampleService#someMethod()
 5       */
 6      public void someMethod() {
 7              System.out.println("Useless implementation...");
 8      }

The next step is to bind the implementation to the interface.

Modules: binding services interface and implementations together

Guice, Hivemind and Tapestry IoC frameworks have the concept of module. A module is a set of services declarations and the corresponding bindings to the chosen implementation. Module implementations are specific to the underlying IoC framework. For example, a Guice's module is a class that implements a specific interface. In Tapestry a module is a class with specific signature method and in Hivemind it is an XML file (although the 2.0 version will support classes as well).

IoC Utils borrows this concept. Modules are classes that implement the IIoCModule interface. As such the module for the service defined above is as follows:

 1  public interface SampleModule implements IIoCModule {
 2  
 3      /**
 4      * @see pt.digitalis.utils.ioc.modules.IIoCModule#configure(pt.digitalis.utils.ioc.modules.IoCBinder)
 5      */
 6      public void configure(IoCBinder binder) {
 7              binder.bind(ISampleService.class, SampleServiceImpl.class).asSingleton();
 8      }
 9 }

The module uses the provided binder to bind the implementation to the service. From this point forward the service will be referred by the interface and not by the implementation. Thus, it will be loosely-coupled.

The bindings can define one of the following instantiation scopes:

  • A singleton instance
  • A "per-thread" instance.

On the previous example, the binding is defined with a singleton scope. To get a "per-thread" instantiation policy the asSingleton() method call may be omitted. See the example below:

1       ...
2       // AnotherSampleServiceImpl will be instantiated in a "per-thread" base
3       binder.bind(ISampleService.class, AnotherSampleServiceImpl.class);
4       ...

Registry initialization

Prior to service implementation the IoC must be initialized with the services stored on the registry. IoC Utils provides the IoCRegistryGuiceImpl Guice registry implementation. This implementation does the necessary service instantiation on the internal Guice IoC container.

Default instantiation

The default IoC Guice implementation can be done as follows:

 1  IIoCRegistry registry = IoCRegistryGuiceImpl.getRegistry();

The registry will be created and returned. A couple of things are worth mentioning:

  • The default strategy to find modules is the FAST method (more on this later).
  • The registry is instantiated as a singleton. This causes that the initialization stage happens only once.

Module finder

IoC initialization includes a module finding step. This find searches the available resources for files named "modules.properties". These files define the module classes, that is the classes that contain service bindings.

Classpath resource finding can be troublesome depending of the environment. For this reason IoC Utils provides three methods to perform module search (defined in the ModuleParser enumeration):

  • FAST: Uses the class path utils getSystemResources for fast detection. Default method.
  • CRAWLER: Searches all class path entries in the file system, opening up directories and ZIP (JAR) files
  • PARANOID: When all else fails, combines both the previous methods for complete discovery. For "ClassLoader nightmare" solving, but slow.

To force a specific module finding method the following code can be used:

 1  IIoCRegistry registry = IoCRegistryGuiceImpl.getRegistry(ModuleParser.CRAWLER);

Declaring Modules

As stated before modules are declared by the use of the module.properties files. Here's an example of such a file:

 1  modules=pt.digitalis.SampleModule

The example declares the previously presented sample Module class. A comma-separated list can be used to declare many modules:

 1  modules=pt.digitalis.SampleModule,pt.digitalis.YetAnotherModule

IoC Utils will collect all these classes and pass them to the underlying IoC framework initializer.

Accessing a service implementation

The following code shows how to access a service's implementation.

 1  ISampleService service = IoCRegistryGuiceImpl.getImplementation(ISampleService.class);

The available service implementation will be returned. If there is more than one implementation available for this interface, an error will be generated, since the method can only return one.

Note: Multiple implementation for the same interface is a feature not supported by some IoC containers. The use of multiple-implementation services will be discussed below.

Injecting dependencies in an already created object instance.

Although a not very common practice, dependency injection on instantiated objects might be needed. These might include situations when the object creation is done by an external entity (such as factory). On such a scenario automatic dependency injection can't occur IoC Utils provides a way to do so: the injectDependencies method. This method allows to inject all dependencies declared in the object class after it's creation.

Here's a usage example:

 1  ISampleService service = Factory.getService();
 2  service = IoCRegistryGuiceImpl.injectDependencies(service);

Multiple Implementations of the same interface

Some times it is useful to provide several implementations for a given service. For example a reporting tool may export to several formats. Additional media support might be supplied in independent JARs. This would enable the possibility to distribute distinct media support to different clients. IoC Utils allows this option without any further configurations or programmer interaction.

The sample application section shows how to accomplish this.

Overriding and preventing overriddance of service bindings

Override a an external service

Some times we need to be able to change a default implementation for some service. This is easy when you own the source of this service. All you need to do is change the binding to your class in the Module source code.
But how can you do this when the contributions comes from a closed source package (like an external JAR)?

You can use .override().

    binder.bind(ISomeService.class, CustomServiceImpl.class).override();

This will replace the default implementation with your own.

Preventing overriddance

The previous functionality might not be desirable in some cases. If you build an API and wish to distribute it but keep some core functionalities untouched, this could be a problem.
For such cases IoC Utils has the .asFinal() bind. See bellow:

    binder.bind(ISomeService.class, ServiceImpl.class).asFinal();

This will declare this bind has unchangeable. It cannot be overridden.
This way you can distribute your API assured that this will always be the service that will be called.

Uncovered features of IoC Utils

Some key features have not been covered here since they're not part of IoC Utils abstraction layer or are technology-specific. To find information on such features for the default IoC implementation check the Google Guice 1.0 User's Guide.

Here's the list of uncovered features:

Declaring dependency injection

All IoC utils provide means for declaring a dependency injection of a service in another service. The dependency injection declaration can be done either programatically, by use of annotations or through XML configuration files.

Binding Scopes

The available scopes are:

  • Singleton: Only one instance will be created and it will always be returned to all callers.
  • "Per-thread" or Threaded: A new instance will be created for each call.
  • Session: A new instance per user session.

Check the documentation of the underlying IoC framework to see if more are available.

Eager and Lazy loading

Eager loading will load all singleton objects at startup time. This will slow down development environments but remove the bad effect of latency time of first access of production environments. On the other hand, lazy-loading will defer object creation and loading to the first time they're requested.

Implicit bindings

An interface can declare the class implements it. This allows the binding resolution in execution time.

Providers or service proxies

Providers or service proxies can be used to control policies. Service implementations can be instantiated one time only or one can have several instances running if needed. Some IoC frameworks, like Hivemind or Tapestry, provide implicit proxies for every object, making it transparent to the user.

And more...

Many other features are supported by each IoC container, but that's beyond the scope of this document. Check the used IoC container documentation for those features.