HK2 - Dependency Injection Kernel

A light-weight and dynamic dependency injection framework

Extensibility

Compatibility

This page describes the features of the HK2 2.0 API. The Habitat API from version 1.x of HK2 has been replaced with a new interface called ServiceLocator. More information can be found here.

Features of HK2

HK2 has many features for customizing the system. This page is intended to give an overview of each feature. Among the set of HK2 features are:

Events

It is possible to send messages from one service to another using the HK2 event feature. The event feature is allows for unrelated services to message each other without prior coordination (other than on the Type of event). It is a pluggable event service, which allows for user defined qualities of service between the publishers and subscribers.

The HK2 event service is described fully here.

An example of plugging in a different event distributor can be found here.

Adding a Scope and Context to the system

In HK2 a Context is a class that is used to control the lifecycle of service instances. A Scope is an annotation that is put onto another annotation that is used to associate any service with a particular Context. All services in HK2 are associated with a single scope.

There are two system provided scope/context pairs. The default Scope for services annotated with @Service is the Singleton scope. Service instances in the Singleton scope are created once and are never destroyed.

The default Scope for services bound with the DynamicConfiguration bind call is PerLookup. Service instances in the PerLookup scope are created every time that the service is injected or looked up via the API. These instances are destroyed when the ServiceHandle destroy method is called on any service that has injected a PerLookup object.

Any number of other scope/context pairs can be added to the system. In order to do so, the user must write an implementation of Context where the parameterized type of the Context is the annotation annotated with Scope that the Context is handling. This implementation of Context is then bound into the ServiceLocator like any other service.

To make this more clear, we have two examples of user scope/context pairs:

  • This example adds a context that is based on the current running tenant.
  • This example adds a request scoped context.

PerThread Scope

There is a per-thread scope/context pair optionally supported in HK2. Services marked with PerThread have their life cycle defined by the thread they are on. Two different threads injecting a service from the PerThread scope will get different objects. Two objects on the same thread injecting a PerThread scope service will get the same object.

The PerThread scope can be added to any ServiceLocator by using the method enablePerThreadScope

InheritableThread Scope

There is a inheritable thread scope/context pair optionally supported in HK2. Services marked with InheritableThread are similar to PerThread scoped services with one caveat, their life cycle defined by the thread they are on and are inherited by its child threads. Two different threads injecting a service from the InheritableThread scope will get different objects. Two objects on the same thread injecting a InheritableThread scope service will get the same object. Two objects on a parent and child thread injecting a InheritableThread scope service will get the same object.

The InheritableThread scope can be added to any ServiceLocator by using the method enableInheritableThreadScope

Immediate Scope

There is an Immediate scope/context pair optionally supported in HK2. Services marked with Immediate will be started as soon as their ActiveDescriptors are added to the ServiceLocator. They are destroyed when their ActiveDescriptors are removed from the ServiceLocator. Immediate services are started and stopped on an independent thread. Users of this feature can also register implementations of ImmediateErrorHandler in order to catch errors thrown by Immediate services.

Care should be taken with the Injection points of an Immediate service, as they will implicitly get created immediately in order to satisfy the dependencies. Since Immediate services are created using an independent thread there is no guarantee that Immediate services will be started before or after any other service. The only guarantee is that Immediate services will eventually get started. Normally they get started very quickly after being added to the ServiceLocator.

The Immediate scope can be added to any ServiceLocator by using the method enableImmediateScope. It is important to notice that enableImmediateScope must be called on all ServiceLocators who will have Immediate services bound in them. In particular it is NOT sufficient to call enableImmediateScope on the parent of a ServiceLocators, since this implementation will only automatically detect Immediate services directly added to the ServiceLocators given to the enableImmediateScope method.

Proxies

Rather than injecting an instance of a service itself, HK2 can also inject a proxy to that service. There are a few reasons that you might want to use proxies. One reason is because the lifeycle of two different scopes may be different. For example, you might have something like a RequestScoped scope, and you would like to inject it into a Singleton scoped object. But the Singleton scoped object is only injected once, and the RequestScoped service will be changing every time the Request has changed. This can be solved by injecting a proxy into the Singleton scoped object. Then every time the Singleton scoped service uses the RequestScoped service the proxy will make sure to use the real RequestScoped service that is appropriate for the current request.

Another reason you might want to use a proxy for a service is if the service is extremely expensive to create, and if possible you want to delay the creation until the service is actually used by the caller. In fact, if the caller never invokes on the proxy, it is possible the service will never get started! This can be done by injecting a proxy into a service rather than the real service. The proxy will not attempt to create the service until some method of that proxy is invoked.

All proxies created by HK2 will also implement ProxyCtl. ProxyCtl can be used to force the creation of the underlying service without calling any of the methods of that service. Of course every service that is to be proxied must be proxiable, so the service to be proxied must either be an interface or a class that is not declared final, has no final fields or methods and has a public zero-argument constructor. In general it is better to proxy interfaces rather than classes.

In order to have HK2 create a proxy for your service rather than the service itself you can create a proxiable scope. A proxiable scope is just like a normal scope, except that the scope annotation is also annotated with Proxiable. All services injected or looked up from this scope will be given a proxy rather than the real service.

This is an example of a proxiable scope:

@Scope
@Proxiable
@Retention(RUNTIME)
@Target( { TYPE, METHOD })
public @interface ProxiableSingleton {
}

While normally every service in a proxiable scope is proxiable, you can override the default proxying behavior on a per-service basis. This is also true for services in non-proxiable scopes. For example you can make a service that is in Singleton scope (which is not proxiable) be proxied. You do this by setting the field isProxiable. If that method returns null then that service will use the scopes mode when it comes to proxying. If that method returns non-null then the system will either proxy or not proxy based on the returned value. Classes that are automatically analyzed can also use the UseProxy annotation to indicate explicitly whether or not they should be proxied. This is a service in Singleton scope that will be proxied:

@Singleton @UseProxy
public class SingletonService {
}

This is a service in the ProxiableSingleton scope that will NOT be proxied (even though ProxiableSingleton is a Proxiable scope):

@ProxiableSingleton @UseProxy(false)
public class AnotherService {
}

Proxying within the same scope

By default if a service is proxiable then it will be proxied even when being injected into other services within the same scope. This allows for the lazy use case. However, it is sometimes the case that it is counter-productive to proxy services when they are injected into other services of the same scope. HK2 supports Proxiable scopes that do NOT proxy services when they are being injected into the same scope. The Proxiable annotation has a field called proxyForSameScope that by default is true but which can be set to false. The following scope is a proxiable scope where services injected into other services in the same scope will not be proxied:

@Scope
@Proxiable(proxyForSameScope=false)
@Retention(RUNTIME)
@Target( { TYPE, METHOD })
public @interface RequestScope {
}

Individual descriptors can also explicity set whether or not they should be proxied for other services in the same scope by setting the isProxyForSameScope value. This value can also be set when using automatic class analysis by using the ProxyForSameScope. The following service is in the ProxiableSingelton scope which would normally not proxy when being injected into the same scope, but which in this case WILL be proxied even when injected into another service in the same scope:

@RequestScope @ProxyForSameScope
public class ExpensiveRequestService {
}

Proxies and equals()

HK2 treats the equals method of Object slightly differently from other methods. If the incoming parameter of the equals method is itself a proxy, then HK2 will unwrap that object and send in the unproxied object. This is to ensure that two proxy instances that are pointing to the same underlying service will return true even if the equals method is not explicitly implemented. Consider the following example:

Foo foo1 = locator.getService(Foo.class);
Foo foo2 = locator.getService(Foo.class);
Assert.assertTrue(foo1.equals(foo2));

In the example above if foo1 and foo2 are proxies that point to the same underlying service then if hk2 did NOT treat equals specially the assertion of truth would fail, since foo1 would have its equals method called being given the proxy of foo2, not the underlying object of foo2. Instead, because equals is treated specially, the assertion will be true since foo2 will be unwrapped and the underlying object will get passed in.

No other method is treated in this way by the proxying code of HK2.

ClassLoading

Classloading is an interesting challenge in any Java environment. HK2 defers classloading as long as possible, but at some point, it must get access to the true class in order to create and inject instances. At that moment, HK2 will attempt to reify the descriptor, using the ServiceLocator reify method.

Every Descriptor bound into the system has an associated HK2Loader. If the getLoader method of Descriptor returns null, then the system defined algorithm for loading classes will be used. Otherwise, the given HK2Loader will be used to load the class described by this Descriptor.

The system algorithm used when the getLoader method of Descriptor returns null is to first consult the classloader of the class being injected into, if available. If not available, HK2 will use the classloader that loaded HK2 itself. Failing this, the class will fail to be loaded and an exception will be thrown.

Note that since the user is providing an implementation of HK2Loader rather than a java.lang.ClassLoader that it is possible to delay the instantiation of the underlying ClassLoader until the Descriptor is being reified. It might also be possible to have the implementation of HK2Loader consult several underlying ClassLoaders, or construct the class dynamically using weaving or some other class building technology. The mind boggles at all the ways HK2Loader can be implemented.

Custom Injection Resolvers

By default the system provides JSR-330 standard injection. That means honoring @Inject and all other parts of the JSR-330 specification. However, it is sometimes the case that a user would like to customize the JSR-330 resolution in some manner, or provide their own injection points based on a different annotation.

In order to do so, the user implements InjectionResolver. The parameterized type of the InjectionResolver must be the injection annotation that they will resolve. The user implementation of InjectionResolver is then bound into a ServiceLocator like any other service.

Annotations to be used as injection points can optionally be annotated with InjectionPointIndicator. This annotation allows automatic analysis of classes using the custom InjectionResolver.

This example adds a custom injection resolver that customizes the default JSR-330 injection resolver.

Just in Time Injection Resolver

There are times when the set of services to be injected is not completely known before the system is booted. In these cases it may be useful to use a Just In Time Injection Resolver. The Just In Time Injection Resolver is an hk2 service that is called whenever the system cannot find a suitable service for a given Injection Point. If the Just In Time Injection Resolver service knows how to find the service then it can add the service to the ServiceLocator and tell hk2 to look again for the service. A good example of using this would be when retrieving services from a remote system, or from some other service oriented system such as OSGi, Spring or Guice.

Security

Certain operations that are performed by the users of HK2 can be validated. Validation can either allow or deny the operation in question. The operations that can be validated are adding a service to the ServiceLocator, removing a service from the ServiceLocator, injecting a service into another service or looking up a service from the ServiceLocator. This feature is most often used in secure use-cases, but has applicability for other use-cases as well. To use validation the user registers an implementation of ValidationService with the ServiceLocator whose operations are to be validated.

There is an example example of how the ValidationService can be used to do a complete security lockdown of the system. This example runs with the J2SE security manager turned on and grants some privileges to some projects and other privileges to other projects to ensure that the ValidationService can be used to define the security of the system.

The example can be seen here.

Instance Lifecycle

A user may register an implementation of InstanceLifecycleListener to be notified whenever an instance of a service is created. Unlike the ValidationService, which deals only with the metadata of a service, the InstanceLifecycleListener is notified whenever an instance of a service is created or destroyed. This is a useful facility for tracing or for scenarios where a service wishes to become an automatic listener for anything that it is injected into.

Interception

AOP Alliance method and constructor interception is supported by HK2. Methods and constructors that are to be intercepted are identified using instances of the HK2 InterceptionService. An example of how to use the InterceptionService can be found here.

There is an HK2 provided default implementation of the InterceptionService which uses annotations to indicate services that should be intercepted, those that should do the intercepting and to bind intercepted methods and constructors to their interceptors. Information about the HK2 provided default implementation of the InterceptionService can be found here.

Dynamic Configuration Listeners

A user may register an implementation of DynamicConfigurationListener to be notified whenever the set of ActiveDescriptors in a ServiceLocator has changed. The DynamicConfigurationListener must be in the Singleton scope.

Class Analysis

HK2 often needs to look at a java class in order to find things about that class such as its set of constructors, methods or fields. The choices HK2 makes is usually determined by specifications such as JSR-330 or JSR-299. However, in some cases different specifications make different choices, or the user of the HK2 system may have some other scheme it would like to use in order to select the parts of class which HK2 should manipulate. For example, the JAX-RS specification requires the system to choose the constructor with the largest number of parameters (by default) while the JSR-299 specification requires the system to choose the zero-argument constructor or else fail.

The HK2 system allows the user to register named implementation of the ClassAnalyzer in order to modify or completely replace the constructors, fields and methods HK2 would choose. Individual HK2 Descriptors can set the name of the ClassAnalyzer that should be used to analyze the implementation class.

HK2 always adds an implementation of ClassAnalyzer with the name “default” that implements the JSR-299 style of selection.

Run Level Services

If your system has sets of services that need to come up and down in an orderly fashion consider using the HK2 Run Level Services. The Run Level Service allows one to specify levels at which services come up and down and will bring these services up and down when the system run level has changed.

Learn more about Run Level Services here.

Self Descriptor Injection

Any service can have its own ActiveDescriptor injected into itself. One use case for this is when you have a common set of services that all share the same super class. The super class can self inject the ActiveDescriptor and then use that to do further generic processing of the service. To self inject the ActiveDescriptor for your service use the Self annotation on a field or on a parameter of your constructor or initializer method. Here is an example:

public abstract class GenericService {
  @Inject @Self
  private ActiveDescriptor<?> myOwnDescriptor;
}

ServiceLocator to ServiceLocator Bridge

It is possible to import all the NORMAL services from one ServiceLocator into another ServiceLocator as long as the locators do not have a parent/child relationship. You do this be creating a ServiceLocator bridge between the two ServiceLocators. This is done using the bridgeServiceLocator method in the optional hk2-extras module.

Bridges are one-way and dynamic. Suppose you have a ServiceLocator named Foo and have bridged its services into the Bar ServiceLocator. If you add a service to the Foo ServiceLocator that service will be locatable in the Bar ServiceLocator, even if the service is added after the bridge was created.

Cycles between ServiceLocators are supported. It will appear that all services from all ServiceLocators are available in all of the ServiceLocators involved in the cycle. For example, if Foo and Bar service locators have bridges going in both directions then it will appear that Foo and Bar have the same set of NORMAL services.

A bridge between two service locators is torn down by using the method unbridgeServiceLocator or by shutting down the ServiceLocator supplying the services.

There are some feature interaction implications for bridged locators. For example if you have a proxiable scope (such as a RequestScope) in a source locator that is bridged to destination locators then when the destination locator gets a reference to the service that object may be a proxy of a proxy. The first proxy is the proxy created by the destination locator and the second proxy is the proxy created by the source locator. Take this into account if you use the __make method of ProxyCtl since it may just return the underlying proxy rather than the end service. Calling methods on services works but may go through layers of proxies. This should be mostly invisible to your application unless you are doing complex work with the proxies.

Error Handling

Errors can pop up in various phases of the HK2 service lifecycle. Users can register implementations of the ErrorService in order to be notified when errors occur. There are currently four types of errors that the system sends to the ErrorService:

  • FAILURE_TO_REIFY: When there has been a problem classloading a service
  • DYNAMIC_CONFIGURATION_FALURE: When a dynamic update to the set of services fails
  • SERVICE_CREATION_FALURE: When a service fails during creation
  • SERVICE_DESTRUCTION_FAILURE: When a service fails during destruction

Using the ErrorService can be a convenient place to standardize on logging of service failures.

Operations

An HK2 Operation is a scope/context pair used to implement scopes like RequestScope, ApplicationScope or TransactionScope. Any service lifecycle (context) for which only one instance of that context can be active at a time is a candidate to be an HK2 Operation. RequestScope is a good example in most containers, as each thread is generally tied to a single request.

More information about HK2 Operations and an example can be found here.

Stub Generation

HK2 has a Stub annotation that can be put onto abstract classes. The hk2-metadata-generator will then generate a class that implements the unimplemented methods on the abstract class. This is very useful when testing as simple stubs can be made of services that would normally not work properly in a test environment. For more information see hk2-metdata-generator.