Writing HK2 Components
Writing HK2 Components
The Hundred-Kilobyte Kernel (HK2) is the lightweight and extensible kernel of Eclipse GlassFish. To interact with Eclipse GlassFish, add-on components plug in to this kernel. In the HK2 component model, the functions of an add-on component are declared through a contract-service implementation paradigm. An HK2 contract identifies and describes the building blocks or the extension points of an application. An HK2 service implements an HK2 contract.
The following topics are addressed here:
HK2 Component Model
The Hundred-Kilobyte Kernel (HK2) provides a module system and component model for building complex software systems. HK2 forms the core of Eclipse GlassFish’s architecture.
The module system is responsible for instantiating classes that constitute the application functionality. The HK2 runtime complements the module system by creating objects. 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 they can then be injected to other objects that need it
Services in the HK2 Component Model
An HK2 service identifies the building blocks or the extension points of an application. A service is a plain-old Java object (POJO) with the following characteristics:
-
The object implements an interface.
-
The object is declared in a JAR file with the
META-INF/servicesfile.
To clearly separate the contract interface and its implementation, the HK2 runtime requires the following information:
-
Which interfaces are contracts
-
Which implementations of such interfaces are services
Interfaces that define a contract are identified by the
org.jvnet.hk2.annotations.Contract annotation.
@Retention(RUNTIME)
@Target(TYPE)
public @interface Contract {
}
Implementations of such contracts should be identified with an
org.jvnet.hk2.annotations.Service annotation so that the HK2 runtime
can recognize them as @Contract implementations.
@Retention(RUNTIME)
@Target(TYPE)
public @interface Service {
...
}
HK2 Runtime
Once Services are defined, the HK2 runtime can be used to instantiate or retrieve instances of services. Each service instance has a scope, specified as singleton, per lookup, or a custom scope.
Scopes of Services
A class marked with @Service has the default scope
jakarta.inject.Singleton. Place another scope annotation on the class
to override that default. HK2 provides
org.glassfish.hk2.api.PerLookup
for a new instance on every lookup. You can also define a custom scope
annotation and register a corresponding
org.glassfish.hk2.api.Context
implementation with the locator.
The following code fragment shows a singleton service (the @Service
default) and a per-lookup service:
@Service
public class SingletonService implements RandomContract {
...
}
@PerLookup
@Service
public class PerLookupService implements RandomContract {
...
}
Instantiation of Components in HK2
Do not call the new method to instantiate components. Instead,
retrieve components by using a
ServiceLocator
instance (org.glassfish.hk2.api.ServiceLocator). ServiceLocator
replaced the old org.jvnet.hk2.component.Habitat type. The simplest
way to use the locator is through a getService(Class<T> contract)
call:
public <T> T getService(Class<T> contractOrImpl, Annotation... qualifiers) throws MultiException;
Use getAllServices when more than one implementation of the contract
may be present. More APIs are available on
ServiceLocator.
HK2 Lifecycle Interfaces
Components can attach behaviors to their construction and destruction
events by implementing the
org.glassfish.hk2.api.PostConstruct
interface, the
org.glassfish.hk2.api.PreDestroy
interface, or both. These interfaces are in hk2-api. They replaced
org.jvnet.hk2.component.PostConstruct and
org.jvnet.hk2.component.PreDestroy. They are interfaces rather than
annotations for performance reasons.
The PostConstruct interface defines a single method, postConstruct,
which is called after a component has been initialized and all its
dependencies have been injected.
The PreDestroy interface defines a single method, preDestroy, which
is called just before a component is removed from the system.
Example 2-1 Example Implementation of PostConstruct and PreDestroy
@Service(name="com.example.container.MyContainer")
public class MyContainer implements Container, PostConstruct, PreDestroy {
@Inject
Logger logger;
...
public void postConstruct() {
logger.info("Starting up.");
}
public void preDestroy() {
logger.info("Shutting down.");
}
}
Inversion of Control
Inversion of control (IoC) refers to a style of software architecture where the behavior of a system is determined by the runtime capabilities of the individual, discrete components that make up the system. This architecture is different from traditional styles of software architecture, where all the components of a system are specified at design-time. With IoC, discrete components respond to high-level events to perform actions. While performing these actions, the components typically rely on other components to provide other actions. In an IoC system, components use injection to gain access to other components.
Injecting HK2 Components
Services usually rely on other services to perform their tasks. The HK2
runtime identifies the @Contract implementations required by a service
by using the
jakarta.inject.Inject
annotation. Inject can be placed on fields or setter methods of any
service instantiated by the HK2 runtime. The target service is retrieved
and injected during the calling service’s instantiation by the component
manager.
The following example shows how to use @Inject at the field level:
@Inject
ConfigService config;
The following example shows how to use @Inject at the setter level:
@Inject
public void set(ConfigService svc) {...}
Injection can further qualify the intended injected service
implementation by using jakarta.inject.Named:
@Inject
@Named("deploy")
AdminCommand deployCommand;
Instantiation Cascading in HK2
Injection of instances that have not been already instantiated triggers
more instantiation. You can see this as a component instantiation
cascade where some code requests for a high-level service will, by using
the @Inject annotation, require more injection and instantiation of
lower level services. This cascading feature keeps the implementation as
private as possible while relying on interfaces and the separation of
contracts and providers.
Example 2-2 Example of Instantiation Cascading
The following example shows how the instantiation of DeploymentService
as a Startup contract implementation will trigger the instantiation of
the ConfigService.
@Contract
public interface Startup {...}
List<Startup> startups;
startups = locator.getAllServices(Startup.class);
@Service
public class DeploymentService implements Startup {
@Inject
ConfigService config;
}
@Service
public class ConfigService implements ... {...}
Identifying a Class as an Add-On Component
Eclipse GlassFish discovers add-on components by identifying Java
programming language classes that are annotated with the
org.jvnet.hk2.annotations.Service annotation.
To identify a class as an implementation of an Eclipse GlassFish service,
add the org.jvnet.hk2.annotations.Service annotation at the
class-definition level of your Java programming language class.
@Service
public class SamplePlugin implements ConsoleProvider {
...
}
The @Service annotation has the following elements. All elements are
optional.
name-
The name of the service. The default value is an empty string.
metadata-
Additional key/value metadata written into the inhabitants file.
analyzer-
The name of the
ClassAnalyzerservice used to analyze this class.
Scope is not an @Service element. Put a scope annotation such as
@PerLookup on the class, or omit it to keep the default singleton
scope. To create a service with a factory, implement
org.glassfish.hk2.api.Factory
instead of pointing @Service at a factory class.
Example 2-3 Example of the Optional Elements of the @Service Annotation
The following example shows how to use the optional name element of
the @Service annotation with a per-lookup scope:
@PerLookup
@Service(name="MyService")
public class SamplePlugin implements ConsoleProvider {
...
}
Using the Apache Maven Build System to Develop HK2 Components
If you are using Maven to build HK2 components, invoke the
hk2-maven-plugin so that the inhabitant metadata is generated
automatically during build time.
Example 2-4 Example of the Maven Plug-In Configuration
<plugin>
<groupId>org.glassfish.hk2</groupId>
<artifactId>hk2-maven-plugin</artifactId>
<configuration>
<includes>
<include>com/example/**</include>
</includes>
</configuration>
</plugin>
Example 2-5 Example of Inhabitant Metadata Generation
The interfaces and classes in this example are as follows:
package com.example.wallaby.annotations;
@Contract
public interface Startup {...}
package com.example.wombat;
@Contract
public interface RandomContract {...}
package com.example.wallaby;
@Service
public class MyService implements Startup, RandomContract, PropertyChangeListener {
...
}
These interfaces and classes generate inhabitant metadata for
MyService that lists the following contracts:
com.example.wallaby.annotations.Startup
com.example.wombat.RandomContract