Extensibility
Compatibility
This page describes the features of the GlassFish HK2 3.0 API. The Habitat API
from version 1.x of HK2 has been replaced with an interface called
ServiceLocator. The references to javax.x in 2.x of HK2 have been replaced with jakarta.*.
More information can be found here.
Features of GlassFish HK2
GlassFish HK2 has many features for customizing the system. This page is intended to give an
overview of each feature. Among the set of GlassFish HK2 features are:
Events
It is possible to send messages from one service to another using the GlassFish 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 GlassFish 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 GlassFish 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 GlassFish 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 GlassFish 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 GlassFish 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
There is an Immediate scope/context pair optionally supported in GlassFish 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, GlassFish 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 GlassFish 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 GlassFish 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. GlassFish 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()
GlassFish 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 GlassFish 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 GlassFish 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 GlassFish HK2.
ClassLoading
Classloading is an interesting challenge in any Java environment. GlassFish 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, GlassFish 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, GlassFish HK2 will use the classloader that loaded GlassFish 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 Jakarta Dependency Injection (DI) standard injection.
That means honoring @Inject and all other parts of the Jakarta DI specification.
However, it is sometimes the case that a user would like to customize the Jakarta DI 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 Jakarta DI 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 a GlassFish 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 GlassFish 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 GlassFish 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 GlassFish HK2. Methods and constructors that are to be
intercepted are identified using instances of the GlassFish HK2 InterceptionService. An example of
how to use the InterceptionService can be found here.
There is an GlassFish 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 GlassFish 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
GlassFish 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 GlassFish HK2 makes is usually determined by specifications
such as Jakarta DI or Jakarta Contexts and Dependency Injection (CDI). However, in some cases different specifications
make different choices, or the user of the GlassFish HK2 system may have some other scheme it would like to use in order to
select the parts of class which GlassFish 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 Jakarta CDI specification requires the system to choose the zero-argument constructor
or else fail.
The GlassFish HK2 system allows the user to register named implementation of the ClassAnalyzer
in order to modify or completely replace the constructors, fields and methods GlassFish HK2 would choose.
Individual GlassFish HK2 Descriptors can set the name of the ClassAnalyzer
that should be used to analyze the implementation class.
GlassFish HK2 always adds an implementation of ClassAnalyzer with the name “default” that implements
the Jakarta CDI 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 GlassFish 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 GlassFish 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
A GlassFish 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 a GlassFish HK2 Operation. RequestScope is a good example in most containers,
as each thread is generally tied to a single request.
More information about GlassFish HK2 Operations and an example can be found here.
Stub Generation
GlassFish 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.