Document Properties
KbidQ23740
Last Modified04-Feb-2020
Added to KB25-Jan-2013
Public AccessEveryone
StatusOnline
Doc TypeGuidelines, Concepts & Cookbooks
Product
  • ICM 7.6
  • ICM 7.7
  • ICM 7.8
  • ICM 7.9
  • ICM 7.10

Concept - Dependency Injection and ObjectGraphs

1 Introduction

The Intershop platform allows dependency injection via the Guice framework. This enables type-safe binding of implementations to interfaces, simple injection of members (e.g., @Inject UserMgr userMgr; ...), and makes it possible to remove static singleton access from the code.

Until now, dependencies between objects have been implicit: Somewhere in the code, some manager is used. This makes dependencies hard to detect and document, and even harder to reduce.

The Guice framework is a lightweight DI implementation that is well-documented and (now) widely used. It uses annotations and generics to declare dependencies using Java language features only. Because of that, there are no external (XML) files to be kept in sync with the actual classes (e.g., no class names as strings). Also, type safety and other typical DI problems are enforced by the compiler and supported by every Java IDE without the need for additional plug-ins.

2 Glossary

Term

Description

Binding

The assignment of an implementation to an interface.

Injector

Guice's term for the →object graph builder.

Module

A configuration class which declares →Bindings to the →Injector.

Object graph

(Guice usage) An object graph contains all objects that are needed to satisfy the dependencies of the root object (and all dependencies of all the dependencies). Or, as Google states: "To construct an object, you first build its dependencies. But to build each dependency, you need its dependencies, and so on. So when you build an object, you really need to build an object graph."

ObjectGraph

(Intershop-specific usage) An object graph representation consisting of a configured →Injector and a set of associated →Modules
There can be multiple object graphs.

Provider

Can be used to retrieve instances of classes without immediately injecting them.

Scope

A decorator for →Provider to define the visibility of instances per →Binding; predefined scopes are: Singleton, Request, Session.

3 Overview

Note

Since 7.0, the dependency injection mechanism replaces the usage of the NamingMgr for registering and using managers and providers.

3.1 Involved Cartridges

  • pf_objectgraph: contains the object engine and abstract object graph implementation
  • pf_objectgraph_guice: contains the Guice implementation of the object graph
  • tools: contains the Guice libraries

The pf_objectgraph cartridge contains the logic to instantiate ObjectGraphs being managed by an ObjectGraphEngine.

The actual DI-framework-specific ObjectGraphEngines are responsible for building and managing their ObjectGraphs.

The pf_objectgraph_guice cartridge contains one such implementation of an ObjectGraph based on the Guice framework. It is responsible for:

  • Loading the object graph properties (i.e., finding modules)
  • Providing an API for the legacy world to request instances and providers from an object graph
DSBootstrapFrameworkObjectGraphClassDiagram

In Guice, an object graph is typically represented by an Injector. Since Injector is a Guice class, using it in Enfinity / Intershop code would lead to a hard dependency on Guice.
This is avoided by using the ObjectGraph interface and only binding an implementation (GuiceObjectGraph) encapsulating the Guice Injector to it. That way, all classes needing object graph access only depend on the ObjectGraph interface.

On application startup, all object graph declarations and modifications for all cartridges from cartridgelist.properties are read and processed (see below).

3.2 Object Graph Configuration

3.2.1 Object Graph Configuration - Object Graphs

The object graphs and the modules (=collections of bindings) for an object graph can be declared in a property file resources/<cartridgeName>/objectgraph/objectgraph.properties for each cartridge (i.e., each cartridge can add modules to object graphs).

resources/core/objectgraph/objectgraph.properties
# Guice-specific settings
global.modules=com.intershop.beehive.core.internal.modules.CoreNamingModule
global.overrideModules=com.customer.application.intershop.extensions.CustomerServletModule

The identifier global declares that the modules are to be used in the ObjectGraph named "global".

  • Base Modules are defined by the <graphID>.modules=A B C entry in the objectgraph.properties and will get loaded into the Injector of object graph graphID. Bindings defined in these modules must be disjoint, so you can only add new bindings. The only exception is the Guice's Multibinding mechanism, where you can add items to a previously bound Multibinder or MapBinder.
  • Override Modules are defined by <graphID>.overrideModules=A' D entries in the objectgraph.properties. These modules are loaded into the injector of object graph graphID and are able to override (replace) bindings defined by base modules of this object graph.

Note

At the moment, all injections in the server (incl. calls via NamingMgr) use only the object graph "global". When creating a different application (e.g., from the test runner), another object graph could be used (to allow easy mocking). This is done by calling NamingMgr.setObjectGraph(objectGraphEngine.getObjectGraph("<graphID>")); before instantiating the first classes.
In addition, any ObjectGraph may be used to instantiate a class, and will automatically be used for injecting any annotated fields needed for that class.

3.2.2 Object Graph Configuration - Modules

Each module contains bindings defining which implementation to use for injection of given interfaces.

Note

Modules are Java classes. Guice bindings cannot be declared in external files (e.g., XML). While this makes it a bit more difficult to change bindings, it enforces compile-time consistency (i.e., a non-existing implementation cannot be bound etc) and reduces the potential for errors.

This also means that overriding is not done implicitly anymore: You have to declare a module to be an overrideModule. Use this functionality with caution and only for bindings that actually override previous bindings.

No overrides in the standard product

Overriding is a means of customization for partners or customers and should not be used in the standard product.

A module might look like this:

CoreNamingModule (excerpt)
public class CoreNamingModule extends AbstractNamingModule
{
    @Override
    protected void configure()
    {
        ...
        // bind a simple manager
        bindManager(LocaleMgr.class).to(LocaleMgrImpl.class).in(Singleton.class);
        ...
        // bind two different PasswordValidators using "named bindings"
        bindProvider(PasswordValidator.class, "PasswordExpressionValidator", named("Expression"))
                .to(PasswordExpressionValidator.class).in(Singleton.class);
        bindProvider(PasswordValidator.class, "PasswordHistoryValidator", named("History"))
                .to(PasswordHistoryValidator.class).in(Singleton.class);
        ...
        // bind a simple service
        bindService(PermissionMgmtService.class).to(PermissionMgmtServiceImpl.class).in(Singleton.class);

    }
    ...
}

3.3 Using (Injecting) Instances

Field injection (see below) now works in pipelets and preparers by default. Managers, providers and services support the whole set of injection methods (field injection, constructor injection, setter injection). Also, all classes instantiated by Guice can use injection. For details, take a look at the module classes of the appropriate cartridge.

All managers, providers and services managed by the NamingMgr can be injected.

Note

Intershop's policy for now is to use only field injection (as in the example below).
For some objects, the control of the instance lifecycle should not be handed over to Guice. This is especially important for POs, BOs, or pipelets. Objects like this are not instantiated by Guice. Only field injection is made possible by the code controlling their lifecycle.

Example Pipelet
import javax.inject.Inject;
...
public class SomePipelet extends Pipelet
{
    ...
    @Inject
    protected SessionMgr sessionMgr;

    @Inject
    private DomainMgr domainMgr;

    @Inject
    private AuthorizationMgr authMgr;

    @Inject
    private UserPOFactory userFactory;

    @Inject @Named(StringEncodingUtils.ENCODING_HTML) 
    private StringEncodingProvider htmlEncoder;
    ...
}

The injected fields can the be used directly.

3.3.1 Injection Is Supported in Classes

  • Declared classes at guice modules
    • former Managers and Providers
  • Instances created by the Component FW, e.g.,
    • BusinessObjectExtensionFactories
    • ServiceDefinitions
  • Pipelets
  • DBInit and DBMigrate Preparer

3.4 Using Injection in Unsupported Classes

Unsupported classes are classes which are not instantiated by the Guice injector and the context was not injected by the class that instantiates the object. Using injection in such classes is a little bit sham, because the object itself defines the context and that is not the intention of dependency injection. Using the annotation @Inject in that cases can be a preparation, nothing more. The question is: How to support instantiation and injection in such classes?

3.4.1 Factory Classes

The instantiation of objects should be located in a factory class:

public interface MyFactory
{
    MyObject createMyObject(parameter ...);
}

and the according implementation:

public class MyFactoryImpl implements MyFactory
{
    @Inject
    private Injector injector;
    @Inject
    private CurrentApplicationBOProvider provider;
 
    MyObject createMyObject(parameter ...)
    {
      MyObject result = new MyObjectImpl(parameter);
      injector.injectMembers(result);
      return result;
    }
 
    MyObject createMyObjectWithInjection(parameter ...)
    {
      return new MyObjectImpl(provider, parameter);
    }
}

At the beginning, it looks a little bit confusing (additional interface and implementation). But if you collect the instantiation of your cartridge in that factory class you have several advantages:

  • The object creation logic is placed at a one place and can be replaced.
  • You can decide to call injectMembers(result).
  • You can avoid injection, using constructor to add injectable objects, without adding parameters to the factory method.
  • You can change the implementation class without adapting all classes which instanciate the implementation class.
  • It is possible to put multiple methods in such a class, so the number of classes is increased by TWO.
  • The implementation of the factory class can be replaced in projects easier than replacing all implementation, which using new operator to instantiate that class.
  • You can instantiate the class MyObjectImpl in test cases in another context (e.g., using mocks to encapsulate the test).

Last but not least, the binding is necessary in your cartridge or test module.

bind(MyFactory.class).to(MyFactoryImpl.class).asEagerSingleton();

Now your implementation does not depend on the injection context (injector). But now you need to get access to the factory, it does not solve the original problem to get access to an object, which is injectable, you need access to an object graph.

3.4.2 Injection in Business Objects

Most business object does not support injection for performance reasons, so @Inject MyFactory; is not available. But the BusinessObjectContext contains the object graph, which was used to create the context.

class MyBusinessObjectImplWithFactory implements BusinessObject
{
    private MyObject createMyObject()
    {
        return getContext().getObjectGraph().getInstance(MyFactory.class).createMyObject(parameter);
    }
}
 
class MyBusinessObjectImplWithoutFactory implements BusinessObject
{
    private MyObject createMyObject()
    {
        MyObject result = new MyObjectImpl(parameter);
        getContext().getObjectGraph().injectMembers(result);
        return result;
    }
}

3.4.3 Other Cases

First, try to get an object graph or Guice injector. This will avoid the fallback to the global single static object path.

If that is impossible, the last option is to use the global object graph of the NamingMgr.

class MyImplWithoutFactory
{
    private MyObject createMyObject()
    {
        return NamingMgr.getObjectGraph().getInstance(MyFactory.class).createMyObject(parameter);
    }

    private MyObject createMyObject()
    {
        MyObjectBO result = new MyObjectImpl(parameter);
        NamingMgr.injectMembers(result);
        return result;
    }
}

3.5 Scopes

Normally, Guice will create a new instance each time a value is injected. This is perfect for objects that are stateless and inexpensive to instantiate.
The @Singleton scope will be used where previously GoF Singletons (static access) were used, i.e., managers, providers etc.

The scope of an object can be defined:

  • In bindings (recommended for flexibility)

    import javax.inject.Singleton;
    import com.google.inject.AbstractModule;
    ...
    public class CoreNamingModule extends AbstractModule
    {
        ...
        @Override
        protected void configure()
        {
            ...
            bind(NamingMgrImpl.class).in(Singleton.class);
            ...
        }
        ...
    }
    
  • As an annotation of the class:

    import javax.inject.Singleton;
    
    ...
    
    @Singleton
    public class NamingMgrImpl extends NamingMgr
    {
        ...
    }
    
  • By annotating @Provides methods

    import javax.inject.Singleton;
    import com.google.inject.Provides;
    ...
    public class SomeModule
    {
        ...
        @Provides @Singleton
        public ApplicationServerMgr getApplicationServerMgr(ServerEnvironment serverEnvironment)
        {
            return serverEnvironment.getApplicationServerMgr();
        }
        ...
    }
    

3.6 Dependency Injection on Startup

At the server startup, all object graph declarations and modifications for all cartridges from cartridgelist.properties are read and processed. After that, the bindings are known, and instances can be requested. Guice now also has the information it needs to instantiate classes in the correct order (resolving inter-dependencies).

When an instance of class SomeClass is requested, Guice instantiates the class bound to SomeClass in the processed modules. If for example, SomeClassImpl is bound to SomeClass, it will instantiate SomeClassImpl. All required fields which are annotated using @Inject will be instantiated too, and so on for all classes needed (recursively).

Since the object graph can be accessed via the NamingMgr, the server startup works almost as before.

Disclaimer

The information provided in the Knowledge Base may not be applicable to all systems and situations. Intershop Communications will not be liable to any party for any direct or indirect damages resulting from the use of the Customer Support section of the Intershop Corporate Web site, including, without limitation, any lost profits, business interruption, loss of programs or other data on your information handling system.

Customer Support
Knowledge Base
Product Resources
Support Tickets