4

I have a project using Jersey 2.25 (with HK2 2.5-b30). Originally, I was using the HK2-Guice Bridge. However, this seems to fail unexpectedly for some cases (in particular, cases where Strings are annotated with a custom annotation configured in Guice will work fine when Guice is doing the injection, but fail silently when HK2 is doing it). Because the same object can act differently depending on how it's injected, I'm becoming terrified of using both together.

I'm now switching everything to use HK2, but sadly it seems like HK2 fails for certain cases where Guice would succeed. In particular, it seems HK2 does not like injecting where a type was not explicitly configured. Guice was happy to just create a new instance of these classes and inject recursively, but HK2 not so much. For example,

1. org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at SystemInjecteeImpl(requiredType=TimeRangeRequestValidator,parent=GetWatchlistEventsImpl,qualifiers={},position=-1,optional=false,self=false,unqualified=null,1218743359)

As you can see, the error message isn't very helpful at all. It should be able to create a TimeRangeRequestValidator, which references some other objects, all of which Guice was able to create with no problem. Is there some list of different behaviors between HK2 and Guice so I can track down why this isn't working?

Note that TimeRangeRequestValidator is a class (not an interface) annotated with @Singleton that has a default public constructor and a field annotated with Inject. Guice had no problems instantiating it.

Robert Fraser
  • 10,649
  • 8
  • 69
  • 93

2 Answers2

4

You can also use a greedy JustInTimeResolver. I've written one below:

@Singleton
@Visibility(DescriptorVisibility.LOCAL)
public class GreedyResolver implements JustInTimeInjectionResolver {
    private final ServiceLocator locator;

    @Inject
    private GreedyResolver(ServiceLocator locator) {
        this.locator = locator;
    }

    @Override
    public boolean justInTimeResolution(Injectee failedInjectionPoint) {
        Type type = failedInjectionPoint.getRequiredType();
        if (type == null) return false;

        Class<?> clazzToAdd = null;
        if (type instanceof Class) {
            clazzToAdd = (Class<?>) type;
        }
        else if (type instanceof ParameterizedType) {
            Type rawType = ((ParameterizedType) type).getRawType();
            if (rawType instanceof Class) {
                clazzToAdd = (Class<?>) rawType;
            }
        }

        if (clazzToAdd == null) return false;
        if (clazzToAdd.isInterface()) return false;

        ServiceLocatorUtilities.addClasses(locator, clazzToAdd);
        return true;
    }

}

You should take care when using the above resolver as it'll add things into your ServiceLocator that you might not have been expecting. It will also probably not do well with injecting things like Strings or other types like that. Still, might work for your use case.

Will not work if your injection point is injecting an interface!

jwells131313
  • 2,364
  • 1
  • 16
  • 26
  • Yup; this looks like what I need. I'll add some checks to make sure I'm actually injecting something I want to be injecting. I think this is exactly what the Guice bridge uses, but at least doing it explicitly gives more visibility and control. – Robert Fraser Sep 27 '17 at 11:50
1

There are a few extra steps you need to configure HK2 to auto-populate your services:

  1. Make sure that you have annotated your interfaces with @Contract and your implementations with @Service
  2. You need to run the HK2 Metadata Generator during your build. This generates the service files that HK2 needs at runtime to determine what classes implement which contract interfaces.
  3. Use ServiceLocatorUtilities.createAndPopulateServiceLocator() to retrieve a ServiceLocator instance.

Note that the specifics or how it will work for you depend on what framework(s) (such as jersey) you are using. See Using HK2 with Jersey.

John Farrelly
  • 7,289
  • 9
  • 42
  • 52