Introduction

With the HK2 module system, the module system is already responsible for instantiating many classes that constitute the application functionality.

HK2 components runtime will complement the module system so that it creates objects, but it configures such objects by:

  • injecting other objects that are needed by a newly instantiated object.
  • injecting configuration information needed for that object.
  • making newly created objects available, so that it can be then injected to other objects that need it.

Services

HK2 relies on the notion of Service to identify the building blocks or the extension points of an application. Services are just POJO :

  • implementing an interface
  • annotated with a Contract annotation
  • use the hk2-api DSL to declare components

    To clearly separate the contract interface and its implementation, the HK2 runtime needs to understand what interfaces are contract and what implementations of such interfaces are services, so this call for an annotation describing interfaces as a service contract.

    @Retention(RUNTIME)
    @Target(TYPE)
    public @interface Contract {   
    }

    Implementations of such contract should also be annotated with an annotation so the HK2 runtime can identify them as contracts implementations.

    @Retention(RUNTIME)
    @Target(TYPE)
    public @interface Service {
    ...
    }

    see Service for complete reference

Runtime

Once Services are defined, HK2 runtime can be used to instantiate or retrieve instances of services. Each service instance can be scoped to be Singleton, per thread, per application or custom. This allow for greater flexibility.

Scopes

Scopes are services themselves, so they can be custom defined and added to the HK2 runtime before being used by other services. Each scope is responsible for storing the services instances which are tied to itself, therefore HK2 runtime does not rely on predefined scopes (although it comes with a few predefined ones).

@Contract
public abstract class Scope {
    public abstract ScopeInstance current();
}

Example of scoping for service to the predefined Singleton Scope

@Service
public Singleton implements Scope {
...
}

@Scope(Singleton.class)
@Service
public class SingletonService implements RandomContract {
...
}

For the curious reader, you can experiment defining a new Scope implementation and have that scope used on your @Service implementations. You will see that the HK2 runtime use the Scope instance to store/retrieve services instances tied to that scope.

Instantiation

Instantiation of components

In the IoC world, components are not instantiated using a new () method call. Components are retrieved using the Services instance. The simplest way is to do that is through a getComponent(ClassT contract) call although there are more APIs available at Services

    /**
     * Retrieve a service locator via a contract class.
     *
     * <pre>
     * // ExampleContract is an example of a contract type
     * &#064;Contract
     * public interface ExampleContract {
     * }
     *
     * &#064;Service
     * public class Example implements ExampleContract {
     * }
     * </pre>
     *
     * @param contract the contract class
     * @return a contract locator
     */
    <U> ContractLocator<U> forContract(Class<U> contract);

HK2 employs a "lazy instantiation" model. Only after demand exists for a component does that component get instantiated. Demand can come from either injection (@Inject) or through programmatic calls to the Services instances. Note, however, that the Scope of the service also plays a role on the instantiation of the given component.

Lifecycle

Components can attach behaviors to their construction and destruction events by implementing the PostConstruct and/or the PreDestroy interfaces. Interfaces have been used rather than annotations for performance as it is faster to cast component instance to any of these interfaces to check if the component has implemented one of them rather than using reflection on all methods to find if they are annotated.

Constructor Injection

It is possible to use Constructor injection by using the Inject annotation. This is particularly useful when using final variable to hold injected references.

@Service
public class SomeService {

    final SomeContract dependencyOne;
    final SomeOtherContract dependencyTwo;

    public SomeService(@Inject(name="foo") SomeContract one, @Inject(name="bar") SomeOtherContract tow) {
        dependencyOne = one;
        dependencyTwo = two;
    }
}

Run Level Based Lifecycle

Components can also use a RunLevel form of lifecycle. As stated previously, HK2 employs a “lazy instantiation” model for component creation. However, there are situations where the developer may want the instantiation to occur either immediately upon HK2 initialization or otherwise on some more regimented schedule.

see run level services for complete reference

Inversion of Control

Injection

Services usually rely of other services to perform their tasks and the model described above can easily be extended to have the HK2 runtime identify the @Contract implementations required by a service. This call for a new annotation Inject which can be placed on fields or setter methods of any Service instantiated by the HK2 runtime and have that Service retrieved and injected during the service's instantiation by the component manager.

Field :
@Inject
ConfigService config;

Setter method :
@Inject
public void set(ConfigService svc) {..}

Injection can further qualify the intended injected service implementation by using a name and scope from which the service should be available.

@Inject(Scope=Singleton.class, name="deploy")
AdminCommand deployCommand;

Instantiation Cascading

Injection of instances which have not been already instantiated triggers more instantiation. In a sense, you can see this as a components instantiation cascading where some code requests for a high level Service will in turn throuh @Inject annotation requires more injection/instantiation of lower level Services. This important feature is a great tool to keep the implementation as private as possible while relying on interfaces and separation of contract/providers.

Below is the example of how the instantiation of DeploymentService as a Startup contract implementation will trigger the instantiation of the ConfigService.

@Contract
public interface Startup {...}

Iterable<Startup> startups;
startups = componentMgr.getComponents(Startup.class);

@Service
public class DeploymentService implements Startup {

    @Inject
    ConfigService config;
}

@Service
public Class ConfigService implements ... {...}