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.
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 }
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.
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:
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 ...
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.
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:
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):
To force a specific module finding method the following code can be used:
1 IIoCRegistry registry = IoCRegistryGuiceImpl.getRegistry(ModuleParser.CRAWLER);
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.
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.
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);
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.
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.
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.
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:
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.
The available scopes are:
Check the documentation of the underlying IoC framework to see if more are available.
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.
An interface can declare the class implements it. This allows the binding resolution in execution time.
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.
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.