API Overview
Compatibility
This page describes the HK2 3.0 API, which was originally based on Jakarta Dependency Injection (DI) 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:
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.