Roll your own Auto Discovery with Jersey and HK2

25 Jul 2016

I recently read a great post by Justin Grant about Dependency Injection in Jersey and I wanted to update it with a slight improvement I uncovered while working with Jersey 2.23.1.

First off, if you haven’t read Justin’s post and aren’t familiar with HK2 and its use within Jersey, I would encourage you to read it first (link above).

Secondly, while I have had luck getting this approach to work, it may rely on some APIs that are open to change in the future so I cannot comment on whether this approach is in line with the future direction of Jersey.

My particular improvement applies to this specific note in Justin’s blog:

However, with Jersey, the ServiceLocator has been created and populated already, which means that it gets a little complicated.

It turns out there’s actually an easier way to tie into Jersey’s ServiceLocator than the one described in Justin’s blog. This is done by utilizing a JAX-RS Feature and Jersey’s ServiceLocatorProvider

package com.example.di;

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
import org.glassfish.hk2.api.DynamicConfigurationService;
import org.glassfish.hk2.api.MultiException;
import org.glassfish.hk2.api.Populator;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.hk2.utilities.ClasspathDescriptorFileFinder;
import org.glassfish.hk2.utilities.DuplicatePostProcessor;
import org.glassfish.jersey.ServiceLocatorProvider;

public class MyDiscoverableFeature implements Feature {

    @Override
    public boolean configure(FeatureContext context) {
        ServiceLocator locator = ServiceLocatorProvider.getServiceLocator(context);
        DynamicConfigurationService dcs = locator.getService(DynamicConfigurationService.class);
        Populator populator = dcs.getPopulator();
        try {
            populator.populate(new ClasspathDescriptorFileFinder(this.getClass().getClassLoader()),
                    new DuplicatePostProcessor());
        } catch (IOException | MultiException ex) {
            Logger.getLogger(MyDiscoverableFeature.class.getName()).log(Level.SEVERE, null, ex);
        }
        return true;
    }
    
}

This class will cause the HK2 framework to scan your classpath for inhabitants files that are created using the hk2-inhabitant-generator. If you haven’t set that up as part of your build you can do so by adding the following line to the plugins section of your pom.xml:

<!-- ... -->
            <plugin>
                <groupId>org.glassfish.hk2</groupId>
                <artifactId>hk2-inhabitant-generator</artifactId>
                <version>2.4.0-b34</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>generate-inhabitants</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
<!-- ... -->

To activate the class you will need to register your new feature as you would any other Jersey feature:

    @Override
    protected Application configure() {
        // ...

        ResourceConfig config = new ResourceConfig();
        config.register(MyDiscoverableFeature.class);

        // ...
    }

From now on Jersey should automatically find all of your @Contract and @Service classes.