Inhabitant Generators
There are two ways to generate hk2 metadata (called inhabitant files) that can be used by the runtime system to find hk2 services without having to classload those services. The first is the HK2 Metadata Generator, which works with the javac build tool, and the second is the HK2 Inhabitant Generator which can be used at build time with any build system or even post build time with JARs or can be embedded in other tools.
The HK2 Metadata Generator is easy to use, as all it requires is to be on the javac classpath. The HK2 Inhabitant Generator is also easy to use but requires the user to specify lines in the build system files (besides just dependencies). Users can choose whichever tool works for them in their build environment.
HK2 Metadata Generator
The HK2 Metadata Generator will generate hk2 inhabitant files during the compilation of your java files. It is a JSR-269 annotation processor that handles the @Service annotation. The only requirement for using it is to put the javax.inject, hk2-utils, hk2-api and hk2-metadata-generator libraries in the classpath of the javac process.
In this example we use the HK2 Metadata Generator in a Maven based build system:
<dependency>
<groupId>org.glassfish.hk2</groupId>
<artifactId>hk2-metadata-generator</artifactId>
</dependency>
Since Maven uses transitive dependencies this is all you need to add as a dependency during build.
In the following example we use the HK2 Metadata Generator in a gradle based build system:
dependencies {
compile group: 'javax.inject', name: 'javax.inject', version: '1.1'
compile group: 'org.glassfish.hk2', name: 'hk2-utils', version: '2.4.0-b14'
compile group: 'org.glassfish.hk2', name: 'hk2-api', version: '2.4.0-b14'
compile group: 'org.glassfish.hk2', name: 'hk2-metadata-generator', version: '2.4.0-b14'
testCompile group: 'javax.inject', name: 'javax.inject', version: '1.1'
testCompile group: 'org.glassfish.hk2', name: 'hk2-utils', version: '2.4.0-b14'
testCompile group: 'org.glassfish.hk2', name: 'hk2-api', version: '2.4.0-b14'
testCompile group: 'org.glassfish.hk2', name: 'hk2-metadata-generator', version: '2.4.0-b14'
}
HK2 Metadata Generator Options
By default the HK2 Metadata Generator places the output file in META-INF/hk2-locator/default. However this behavior can be modified by setting the option org.glassfish.hk2.metadata.location to the desired location. This is done with the javac compiler using the -A option. In gradle this looks something like this:
compileJava {
options.compilerArgs << '-Aorg.glassfish.hk2.metadata.location=META-INF/hk2-locator/acme'
}
HK2 Inhabitant Generator
The HK2 Inhabitant Generator is a utility that will generate inhabitants file during the build of your JAR file. It works by analyzing the classes that have been built by javac and then creating the file META-INF/hk2-locator/default (by default) in your JAR file that has information in it about all of the classes that you have marked with @Service or @Contract.
The HK2 Inhabitatants Generator can be used as a standalone command-line tool, or it can be embedded in any Java program. It can also be used in a Maven build. An Eclipse build and an ant task are also planned. Here are the ways that the HK2 Inhabitants Generator can be used:
Command Line Tool
The HK2 Inhabitants Genertor can be run from the command line like this:
java org.jvnet.hk2.generator.HabitatGenerator
By default the HabibatGenerator will attempt to analyze the first element of the classpath and replace that element (if it is a JAR) with a new JAR that has an inhabitants file in it describing all of the classes marked with @Service. If the first element of the classpath is a directory it will attempt to create a new inhabitants file in that directory describing all of the classes marked with @Service.
You can modify this behavior by using command line options. Here is the usage statement for HabibatGenerator:
java org.jvnet.hk2.generator.HabitatGenerator
[--file jarFileOrDirectory]
[--outjar jarFile]
[--locator locatorName]
[--verbose]
The –file option allows the user to pick a directory or JAR file to analyze for classes marked with @Service.
The –outjar option allows the user to pick the output JAR file that the generator should create with the inhabitants file in it.
The –locator option allows the user to name the locator that these services should go into. This value is "default" by default.
The –verbose option make the generator print extra information as it does its work.
This command line utility will call System.exit when it is done with a 0 code if it was able to work properly and a non-zero value if it failed in some way.
Embedded Usage
The class org.jvnet.hk2.generator.HabitatGenerator has a static method on called embeddedMain. The embeddedMain takes the typical argv[] array of parameters and so has the same behavior as the command line usage. The biggest difference is that this method returns an int as the return code, either 0 for success or non-zero for failure and does not call System.exit(). See the javadoc for more information.
Using embeddedMain is useful if you want to build your own build tools that generate inhabitants files for your own IDE or other build environment.
Using Maven
The HabitatGenerator is also available as a Maven plugin. It has a single goal, called generateInhabitants that is run in the process-classes phase by default. Using this plugin in your build will cause inhabitants files to be generated in your output directory.
The following example plugin snippet from a pom.xml will run the InhabitantsGenerator in both the main tree and in the test tree, in case you would like your test sources to also be analyzed for classes marked with @Service.
<plugin>
<groupId>org.glassfish.hk2</groupId>
<artifactId>hk2-inhabitant-generator</artifactId>
<version>2.5.0-b36</version>
<executions>
<execution>
<goals>
<goal>generate-inhabitants</goal>
</goals>
</execution>
</executions>
</plugin>
The plugin has the following configuration options:
- outputDirectory (the place the output file will go, defaults to ${project.build.outputDirectory})
- testOutputDirectory (the place the output will go if test is true, defaults to ${project.build.testOutputDirectory})
- verbose (true or false)
- test Set to true if this execution should be for the tests rather than main
- locator The name of the locator file (which is "default" by default)
- noswap (true or false) if set to true the generator will overwrite files in place which is riskier but faster
Ant Task
The inhabitant generator can also be used as an ant task. The ant task is org.jvnet.hk2.generator.maven.ant.HK2InhabitantGeneratorTask. Below find an example ant file that uses the task:
<project name="HK2 Ant Build File" default="build" basedir=".">
<!-- set global properties for this build -->
<property name="src" location="src"/>
<property name="build" location="target/classes"/>
<taskdef name="hk2-inhabitant-generator"
classname="org.jvnet.hk2.generator.ant.HK2InhabitantGeneratorTask"/>
<target name="compile" >
<!-- Compile the java code from ${src} into ${build} -->
<javac srcdir="${src}" destdir="${build}"/>
<hk2-inhabitant-generator targetDirectory="${build}"/>
</target>
</project>
The thing to note in the example above is that the hk2-inhabitant-generator must run after the classes are built, as the hk2-inhabitant-generator inspects the class files.
The ant plugin has the following options:
- targetDirectory (the directory to find the classes, defaults to target/classes)
- outputDirectory (the place the output file will go, defaults to target/classes)
- verbose (true or false)
- locator The name of the locator file (which is "default" by default)
- noswap (true or false) if set to true the generator will overwrite files in place which is riskier but faster
Stub Generation
The HK2 metadata generator can also generate implementation classes based on abstract classes. This is useful when testing, as it is often the case in tests that the user would like to replace some service with one that does nothing or does only a few special things during the test. This is done by putting the Stub annotation on an abstract class. Any abstract methods NOT implemented by the class will have dummy implementations generated in a java file that also has an Service annotation. This will then become an service in the Singleton scope, which should show up when using most hk2 initialization methodologies. The benefits of using the Stub annotation include:
- The service need not be completely implemented. Only those methods that need to return specific data needed by the test need to be implemented
- The Rank annotation will work when placed on the abstract class, so the stub can be given higher priority than the replacement class
- Using Stub means that this abstract class may not need to be updated if the underlying interface or class has an added method
- It makes it very easy to write very simple test replacement classes
In this simple example there is an interface with a large number of methods:
@Contract
public interface ManyMethodsService {
public void methodA();
public void methodB();
public int methodFoo();
public String methodBar(String input);
// And so on
}
Somewhere in your build there is an implementation of ManyMethodsService. It may or may not look like this:
@Service
public class ManyMethodsServiceImpl implements ManyMethodsService {
// Here we really implement the interface, possibly doing JDBC or other possibly heavy operations
}
However, in the test environment there is only one or two methods that the test touches. In this case we can very easily use the Stub annotation to tell the hk2-metadata-generator to generate an implementation that fills in the missing methods. This test version of the service might be implemented like this:
@Stub @Rank(1)
public abstract class TestManyMethodsService implements ManyMethodsService {
// Only implement the methods that the test actually touches, and return
// hard-coded data
public int methodFoo() {
return 13;
}
public String methodBar(String input) {
return input;
}
// Do not implement the other methods in the interface
}
If the hk2-metadata-generator is in the classpath of the test build then it will see the Stub annotation and will generate a java file with the unimplemented methods implemented, returning either null or 0 or “a” along with an added Service annotation. Since the Rank annotation is set to 1 in the abstract class (TestManyMethodsService) it will be used in favor of the true implementation (ManyMethodsServiceImpl).
If there is a @Named qualifier on the class annotated with Stub that qualifier will also be copied to the resulting implementation class. If @Named has no value associated with it the value used will be the value of the class with Stub on it. For example this class:
@Stub @Named
public abstract class AliceService implements SomeInterface {
}
will end up having the name "AliceService", while this class:
@Stub @Named("Bob")
public abstract class AliceService implements SomeInterface {
}
will end up having the name "Bob". Only the @Named qualifier is treated specially in this way.
The Stub annotation can also generate implementations where rather than returning null or zero values it throws UnsupportedOperationException (with the name of the method called in the message). This can be done by setting the value field of the Stub to EXCEPTIONS, like this:
@Stub(Stub.Type.EXCEPTIONS)
public abstract class AliceService implements SomeInterface {
// All generated methods will throw UnsupportedOperationException
}
This can be useful when writing tests to ensure that the code does not inadvertently use one of the methods of the stub’s implementation.