HK2 - Dependency Injection Kernel

A light-weight and dynamic dependency injection framework

API Overview

Compatibility

This page describes the HK2 2.0 API, which is based on JSR-330 standard annotations. Also, Habitat has been replaced with a new interface called ServiceLocator.

Introduction

HK2 is a declarative framework for services using annotations like @Contract and @Service. However, it is possible to use programmatic APIs to precisely control the services and bindings available within the Services registry.

ServiceLocator

The most fundamental service in HK2 is the ServiceLocator. The ServiceLocator represents the registry where services are looked up and where information about services (known as Descriptors) are bound into the registry. The ServiceLocator itself is represented as a service in its own registry; it is always the first service bound into its own registry.

ServiceLocators are named uniquely in a JVM and each has a unique locator ID. It is possible to create or find ServiceLocators using the ServiceLocatorFactory. The ServiceLocatorFactory will normally use a default implementation of ServiceLocatorGenerator specified in META-INF/services. The default implementation can be changed by having a different META-INF/services specification of the implementation of ServiceLocatorGenerator earlier in the classpath than the provided implementation. An implementation of ServiceLocatorGenerator can also be given directly to the ServiceLocatorFactory create method.

Once you have created a ServiceLocator with the ServiceLocatorFactory it will contain at least three services:

Adding in your own services

While the three services in your ServiceLocator are nice, they hardly constitute a useful system. What is needed is all of your services, in order to make it useful. Also please note that this section assumes that you are not using the upper level system that automatically reads in the descriptions of your services and populate ServiceLocators for you. For information on how that system works see here.

You add your own service by using the DynamicConfigurationService. DynamicConfigurationService is one of the set of services automatically added to every ServiceLocator. You can get that service by simply looking it up:

    public void initialize() {
        ServiceLocatorFactory factory = ServiceLocatorFactory.getInstance();
        
        ServiceLocator locator = factory.create("HelloWorld");
        
        DynamicConfigurationService dcs = locator.getService(DynamicConfigurationService.class);
        
        ...
    }

You use the DynamicConfigurationService to create DynamicConfiguration instances. The DynamicConfiguration interface has a few methods for binding in descriptions of your services.

In order to bind in services you need to first create a description of your service. A description of your service gives information about the service, such as the name of the implementation class, and the name of the classes or interfaces which the service should be available to be looked up as, and other information. In general, any implementation of Descriptor can be used, but we have provided at least two mechanisms for creating Descriptors that you might want to use. We will go through those mechanisms in the next two sections, and then come back to adding in your own descriptor to your newly created ServiceLocator.

BuilderHelper Binding EDSL

An EDSL is an Embedded Domain Specific Language that allows you to build up objects specific to your particular domain. In this case we provide an EDSL for building Descriptors.

Lets take an example. Suppose I wanted to tell the system about a service of mine that has implementation class com.acme.internal.WidgetImpl which implements the com.acme.Widget contract (interface) and which is in the PerLookup scope (which means a new instance of WidgetImpl will be provided for every injection point). Here is how a descriptor that contains all of that information can be built up using our EDSL:

    public Descriptor createWidgetDescriptor() {
        return BuilderHelper.link("com.acme.internal.WidgetImpl").
                         to("com.acme.Widget").
                         in("org.glassfish.api.PerLookup").
                         build();
    }

The BuilderHelper link method creates a DescriptorBuilder. The DescriptorBuilder then creates more and more specific versions of itself as you fill in the data with calls to “to” or “in” or “qualifiedBy”.

Finally, when you are finished filling in all the details of your service, you call build in order to produce a Descriptor that can be used in a bind call of DynamicConfiguration.

It is interesting to note that the build call of DescriptorBuilder produces a DescriptorImpl. A DescriptorImpl is nothing more than a convenience implementation of Descriptor that has settable fields. Hence, if your code wanted to use the EDSL to produce a basic Descriptor and then further customize it with the added methods of DescriptorImpl it could do so.

DescriptorImpl

Rather than create your own implementation of Descriptor we have provided an implementation of Descriptor called DescriptorImpl. This implementation has convenient methods for setting all of the fields of Descriptor. It should be noted that the bind API of DynamicConfiguration will make a deep copy of whatever Descriptor is passed to it, and that the underlying implementation of the HK2 API never uses the DescriptorImpl class directly. It is purely there as a convenience class for those who wish to provide their own Descriptors.

Here is an example that achieves the same Descriptor as the example in the previous section but uses the DescriptorImpl to do it:

    public Descriptor createWidgetDescriptor() {
        DescriptorImpl retVal = new DescriptorImpl();
        
        retVal.setImplementation("com.acme.internal.WidgetImpl");
        retVal.addAdvertisedContract("com.acme.internal.WidgetImpl");
        retVal.addAdvertisedContract("com.acme.Widget");
        retVal.setScope("org.glassfish.api.PerLookup");
        
        return retVal;
    }

One interesting thing to notice in the above code is that we added the implementation class as an advertisedContract. This was done automatically for us in the BuilderHelper case, but needed to be explicitly done in this case.

Binding a Descriptor into a ServiceLocator

Now that we have seen two simple ways to create a Descriptor lets take a look at how we bind that descriptor into our ServiceLocator. Here is an example:

    public void initialize() {
        ServiceLocatorFactory factory = ServiceLocatorFactory.getInstance();
        
        ServiceLocator locator = factory.create("HelloWorld");
        
        DynamicConfigurationService dcs = locator.getService(DynamicConfigurationService.class);
        
        DynamicConfiguration config = dcs.createDynamicConfiguration();
        
        config.bind(createWidgetDescriptor());
        
        config.commit();
    }

The method createWidgetDescriptor is from the preceding examples. In the above code we call the createDynamicConfiguration method of DynamicConfigurationService. This creates an instance of DynamicConfiguration. To use a [DynamicConfiguration][dynamiccondfiguration] you call the bind or unbind methods until you are happy with the change and then you call commit to make the changes occur for real in the system. If you do not call commit none of the changes you added to the DynamicConfiguration instance will be made to the system.

That is all there is to it! The services you add in this manner can now be looked up or injected into other services or generally manipulated through all of the other methods in ServiceLocator.

Convenience methods for adding services

There are several convenience methods that we have added to simplify the task of adding descriptors to a service locator. These are encapsulated in the ServiceLocatorUtilities class.

If you already have a service class and you would like for hk2 to automatically analyze the class and add it to a locator then you can use the addClasses method.

If you already have an instance of a service and you would like hk2 to automatically analyze the class of the service and add it to a locator then you can use the addOneConstant method

If you already have a descriptor for a service and you would like hk2 to add it to a locator then you can use the addOneDescriptor addOneDescriptor method

Looking up services

There are several mechanisms for looking up services in HK2. The simplest is to just call getService method of ServiceLocator with the class of the service you are interested in:

  Widget widget = locator.getService(Widget.class);

The type passed in can be any implementation class or interface with which the service was bound with as an advertisable contract. If there is no Widget that can be found in the system then the getService method will return null. If there are more than one Widget (e.g. Widget is an interface that can have many implementations) then the best Widget will be returned from the getService method.

Services are sorted by (in order) the service ranking, the largest locator id (so that services in children are picked before services in parents) and smallest service id (so that older services are picked prior to newer services). Therefore the best instance of a service is a service with the highest ranking or largest service locator id or the lowest service id. The ranking of a service is found in its Descriptor and can be changed at any time at run time. The locator id of a service is a system assigned value for the Descriptor when it is bound into the ServiceLocator and is the id of that ServiceLocator. The service id of a service is a system assigned value for the Descriptor when it is bound into the ServiceLocator. The system assigned value is a monotonically increasing value. Thus if two services have the same ranking the best service will be associated with the oldest Descriptor bound into the system.

Looking up services by name

Services can be qualified in many ways, but the most common is to have a name associated with the service. Hence, in our Widget example if there are several Widgets in the system but each has a different name we can find our particular Widget like this:

    public Widget getNamedWidget(String name) {
        return locator.getService(Widget.class, name);
    }

The given name is used to further qualify the specific Widget that was bound into the system.

Looking up services with qualifiers

If your services have qualifiers you can look them up via the qualifiers. In order to do this you can use the AnnotationLiteral in order to create concrete implementations of your annotations. Lets see how this would be done. Suppose you have a qualifier called Blue, defined like this:

@Qualifier
@Retention(RUNTIME)
@Target( { TYPE, METHOD, FIELD, PARAMETER })
public @interface Blue {
}

Normally you wouldn’t implement Blue, but in this case you do need an implementation in order to be able to look it up. You do that by providing an implement of Blue that extends AnnotationLiteral:

public class BlueImpl extends AnnotationLiteral<Blue> implements Blue {
}

You can now use this BlueImpl to look up your qualified Widget in a ServiceLocator like this:

    Widget widget = locator.getService(Widget.class, new BlueImpl());

This will get the Widget that has been qualified with @Blue.

Getting all services

You may also want to get all of the services that have advertised a certain contract. You can do this like this:

    List<Widget> widgetList = locator.getAllServices(Widget.class);

The list returned will have as many Widgets that could be found in the system. It is important to note in this case that all of the Widgets will have been classloaded when you use this call, so if classloading performance is important to you be careful of using the getAllServices method. Instead, consider using the getAllServiceHandles or getDescriptors method.

Getting service descriptors

If you want to look up service descriptors rather than the services themselves you can use the getDescriptor or getBestDescriptor methods on ServiceLocator. The getDescriptor and getBestDescriptor methods will never cause classloading to occur, so it is safe to use in environments where classloading can be an issue.

The getDescriptor methods on ServiceLocator use a Fitler to determine which Descriptors to return. You can implement your own Filter or you can use one of the Filter implementations provided by BuilderHelper. The most common case is to use an IndexedFilter provided by BuildHelper, like this:

  IndexedFilter widgetFilter = BuilderHelper.createContractFilter(Widget.class.getName());
  
  List<ActiveDescriptor<?>> widgetDescriptors = locator.getDescriptors(widgetFilter);

Using an IndexedFilter can greatly improve the search time for your Descriptors.

Unmanaged Creation, Injection and Lifecycle

There are times when you would like to have an object created, injected or have its lifecycle methods called by HK2, but not have that Object be explicitly managed by HK2. The ServiceLocator has methods that suit this case. These methods will inspect the class or object given and will attempt to perform the requested operations, without keeping track or managing those objects in any way.

The first method is the create method, which will attempt to create an instance of the given class using the dependency injection rules of HK2:

  Widget widget = locator.create(WidgetImpl.class);

It is important to note that the only references to other beans that will have been initialized when this returns are those necessary to perform constructor injection. Hence any @Inject fields or @Inject initializer methods will NOT have been initialized when this method returns.

If you already have an object, and would like for its @Inject fields and @Inject initializer methods to get filled in, you can use the inject method:

  locator.inject(widget);

The object given will be analyzed and all of the fields and methods will be injected upon return. However, any postConstruct method on the object will not have been called yet. That can be done with the postConstruct method:

  locator.postConstruct(widget);

This method call will find the postConstruct method on widget and call it. Once the user is finished with the object, they can force the preDestroy to be called on it by using the preDestroy method:

  locator.preDestroy(widget);

This sequence can be very useful when there is some special processing that needs to happen and the user does not want to have HK2 manage the objects themselves.