Getting started with HK2 - Part II
Posted by ss141213 on January 28, 2008 at 12:29 PM | Comments (5)
Download The Sample
This is a sequel to my last blog where I described a Hello World sample running on HK2. As promised there, I am going to expand that sample to show module management, class loading, component injection, instantiation cascading features of HK2 working.
Structure of the sample
hello-world-hk2-sample2/
/pom.xml
/hello2-startup/pom.xml
/src/main/java/sahoo/hello/startup/MyStartup.java
/hello2-impl/pom.xml
/src/main/java/sahoo/hello/api/Foo.java
/src/main/java/sahoo/hello/api/Bar.java
/src/main/java/sahoo/hello/impl/FooImpl.java
/src/main/java/sahoo/hello/impl/BarImpl.java
The modules are organized the same way as our earlier example except that I have added a new module called hello2-impl. The new module holds some implementation details. It is introduced to show how inter-module dependency is managed by HK2.
Instructions to build and run remain unchanged. You can run mvn -f hello-world-hk2-sample-2/pom.xml install to build the sample. To run it, do one of the following:
mvn -f hello-world-hk2-sample-2/hello2-startup/pom.xml hk2:run
or
hello-world-hk2-sample-2/run.sh
Refer to the earlier blog for an explanation of the above steps.
Let's go through the contents, first our new module, hello-impl.
Step 1. Make it a HK2 module:
To do this, we need to specify packaging type as hk2-jar in its pom.xml. Since this is a custom packaging type, we need to configure the pom.xml with the plugin, hk2-maven-plugin, which provides such support. Since this plugin as well as HK2 modules are not available in standard Maven repo, you have to configure some extra repositories in the parent POM (you can do it in your ~/.m2/settings.xml as well, if you want to avoid doing it every time).
Step 2: Choose the packages you want to export:
Next, let's see how to choose the packages we want to export. By default, HK2 module management exports all packages from a module to other modules. This is just opposite to how OSGi behaves. hello2-impl module has two packages, viz: sahoo.hello.api and sahoo.hello.impl. The former one contains interfaces that we would like to export, where as the latter one contains implementation details that we would like to hide from others. Currently, there is no package level annotations defined in HK2 to mark some packages for exporting, hence we have to manually configure our hk2-maven-plugin in pom.xml to generate the following manifest entry:
HK2-Export-Package: sahoo.hello.api
It is achieved by configuring the hk2-maven-plugin in pom.xml as follows:
<build>
<plugins>
<plugin>
<groupId>com.sun.enterprise</groupId>
<artifactId>hk2-maven-plugin</artifactId>
<version>...</version>
<extensions>true</extensions>
<configuration>
<archive>
<manifestEntries>
<HK2-Export-Package>
sahoo.hello.api
</HK2-Export-Package>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
The above syntax is same as what is applicable to maven-jar-plugin.
Step 3: Define contract classes Foo.java and Bar.java
In HK2, a contract is a Java interface which is annotated with @Contract. Typically a service or a component has two parts, viz: an interface class which defines the public interface of the service and an implementation class. HK2 allows you to develop a contract-less component as well. In HK2, the interface is annotated with @Contract, where as the implementation is annotated with @Service.
If you don't know why HK2 requires you to use @Contract, don't worry, it will be cleared in due course of time.
In our case, both Foo.java
Foo.java and Bar.java have such annotations in place. Keeping the true spirit of modularisation, we have defined Foo and Bar in sahoo.hello.api package, which is the package that is being exported by hello-impl module. The implementations are hidden from other modules.
Step 4:Decide the scope of components
There is something called scope attribute in @Contract. The default value for scope is Singleton.class. As the name suggests, when you ask the component manager for a component, it uses this information to decide whether to create a new instance or return an existing instance. If it decides to return an existing instance, the scope can further control where it searches for an existing instance. Singleton, as the name suggests, implies that there will be no more than one such instance of this contract type. There is another scope defined in HK2 called PerLookup.class which means every time you ask for such a component, a new instance is created by HK2 component manager as if you called the constructor. A very powerful feature of HK2 is the notion of user defined scope. I have a sample ready that shows how to use your own scope, but let me defer discussion on that to another day.
In our case, we are relying on the default scope.
Step 5:Define component classes FooImpl.java and BarImpl.java
They are both defined in a package called sahoo.hello.impl, which is not exported by our module. Now let's take a closer look at our component classes.
A component class must be annotated with @Service.
A component class must have public, no-arg constructor.
A component can depend on other components. To express such dependency, a component can use @Inject annotation. While specifying @Inject, one should specify the contract interface type in order to avoid tight coupling with any given implementation if possible. Our component Foo.java follows all these rules as shown below:
@Service
public class FooImpl implements Foo {
@Inject Bar bar;
public FooImpl() {
System.out.println("FooImpl() called");
}
public void doit() { bar.doit();}
}
A natural question is when does the injected field bar get initialized? It is initialized by HK2 framework after the constructor call, which means you should not be accessing this field in your default constructor. If you need to specify some business logic as part of construction, then make your component implement PostConstruct interface.
Step 6: Write a startup module
So far, we have described about hello-impl module which defines two contracts called Foo and Bar, but how does HK2 know about it? Who uses them? That's where our startup module comes into picture. Remember, in my previous blog, I told this is the primordial module? This is the module that consumes those contracts. Now let's look at the only class that is defined in this module:
@Service
public class MyStartup implements ModuleStartup
{
@Inject Habitat habitat;
@Inject Foo foo;
public void setStartupContext(StartupContext context) {
}
public void run() {
System.out.println("Hello World - My first HK2 Sample");
System.out.println("Injected foo = " + foo);
Foo lookedUpFoo = habitat.getComponent(Foo.class, null);
System.out.println("habitat.getByComponent(Foo.class, null) = " +
lookedUpFoo);
// Since Foo is Singleton scoped, the following assertion holds good
assert(foo == lookedUpFoo);
foo.doit();
}
}
It is itself a component, for it is annotated with @Service. What contract does it have? It's ModuleStartup. Since it is a component, it can use dependency injection. See how it is injected with a Foo object and a Habitat object. What is this Habitat object? Well, Habitat is the component manager in HK2.
Since, hello-startup module uses interfaces from hello-impl module, we have to set appropriate dependency in hello-startup's pom.xml. When we do this, out hk2-maven-plugin is smart enough to generate the following manifest headers in hello-startup's META-INF/MANIFEST.MF:
HK2-Import-Bundles: sahoo:hello2-impl, com.sun.enterprise:hk2
That's it. You are all set to build and run the sample.
Observations
You can see HK2 automatically starting hello2-impl module when it tries to start the primordial module. You can also see instantiation cascading: MyStartup -> Foo -> Bar because of Bar injected in Foo injected in MyStartup. Similarly, you can see Singleton scope in action. Next time, when the code asks for Foo type component to the component manager, it is returned the previous instance. To test package hiding, change the class to load FooImpl.class directly from MyStartup, and you will get a ClassNotFoundException.
Q1. Why does HK2 use @Contract and @Service?
It's an ease of development thing. Even Java SE platform provides a service framework, but that does not require any annotations, but why HK2 requires? Java SE requires programmers to generate META-INF/services file as described here, where as HK2 generates the appropriate meta-information by parsing the annotations at compile time. This is done by HK2 supplied hk2:compile goal.
Q2. What is Habitat in HK2?
This is the component manager in HK2. Think like JNDI Naming Manager in Java EE. Well, it's much more than that actually. More discussion on Habitat some other day. Got to sign off for the day.
Conclusion
I hope you found this useful. As usual, your comments are useful. Stay tuned for more on HK2 and GlassFish.
Bookmark blog post: del.icio.us Digg DZone Furl Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment
-
HK2 looks realy interesting. I would like to play with it. Unfortunatly I cann't build the example. Maven prints the following error message:
[ERROR] BUILD ERROR
[INFO] ------------------------------------------------------------------------
[INFO] Plugin could not be found - check that the goal name is correct: Unable to download the artifact from any repository
Try downloading the file manually from the project website.
Then, install it using the command:
mvn install:install-file -DgroupId=com.sun.enterprise -DartifactId=hk2-maven-plugin -Dversion=0.2-SNAPSHOT -Dpackaging=maven-plugin -Dfile=/path/to/file
Alternatively, if you host your own repository you can deploy the file there:
mvn deploy:deploy-file -DgroupId=com.sun.enterprise -DartifactId=hk2-maven-plugin -Dversion=0.2-SNAPSHOT -Dpackaging=maven-plugin -Dfile=/path/to/file -Durl=[url] -DrepositoryId=[id]
com.sun.enterprise:hk2-maven-plugin:maven-plugin:0.2-SNAPSHOT
from the specified remote repositories:
central (http://repo1.maven.org/maven2)
com.sun.enterprise:hk2-maven-plugin:maven-plugin:0.2-SNAPSHOT
from the specified remote repositories:
central (http://repo1.maven.org/maven2)
What can I do to get the example running?
Thank you.
PS: There seems to be something wrong with this posting-form. It clears it's input when pushing the preview button.
Posted by: alegrn on January 29, 2008 at 01:26 PM
-
Hi alegrn,
Can you download the zip file again and try? I have updated the top level POM to include the extra Maven repositories from where HK2 related artifacts need to be downloaded. Since I had them set up in my ~/.m2/settings.xml, it was working for me. Sorry about that. Thanks. Look forward to your comments...
Sahoo
Posted by: ss141213 on January 29, 2008 at 08:42 PM
-
Hi,
Thank you for this tutorial, I found it very useful.
My question:
How come only Services can inject other Servvices, why can we inject Services into a plain Java class?
For example my situation is like this:
I have a ConfigurationProviderService that gets all the parameters for the executable application on runtime and I need to use this service almost in every java class file, but I can't :( I always get a NullPointerException which means that Service was not initialized. Is there some way to use Services in plain java classes?
I added an extra java class to your example project, so that you can understand what I mean:
2nd-hello-world-hk2-sample\hello2-impl\src\main\java\sahoo\hello\implPlainClass.java
And uploaded the source to:
http://www.zone.ee/antsh/2nd-hello-world-hk2-sample.zip
Just try to execute the project.
Looking forward to your responce,
Anton
Posted by: antsh on April 06, 2008 at 08:26 AM
-
You can't inject into a plain Java class, because its construction is not controlled by anyone. However, if you want to use a Service from such a class, you can use the Habitat. Ask such questions in users@glassfish.dev.java.net.
Posted by: ss141213 on April 06, 2008 at 11:53 AM
-
Thank you for your quick response. I will send a letter to users@glassfish.dev.java.net.
Posted by: antsh on April 06, 2008 at 02:23 PM
|