7

For the life of me I cannot get Jersey with hk2 to automatically discover @Service annotated classes and inject them. I have tried to follow every advice on stack overflow, jersey and hk2 documentation and still no luck. I am trying to inject a simple echo service into a Jersey resource. The skeleton is generated from the simple webapp maven archetype for Jersey, which I tried to extend. This is what I have so far:

pom.xml

<build>
  <finalName>sandbox</finalName>
  <plugins>
    <plugin>
      <groupId>org.glassfish.hk2</groupId>
      <artifactId>hk2-inhabitant-generator</artifactId>
      <version>2.3.0</version>
      <executions>
        <execution>
          <configuration>
            <verbose>true</verbose>
          </configuration>
          <goals>
            <goal>generate-inhabitants</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>
...
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.glassfish.jersey</groupId>
      <artifactId>jersey-bom</artifactId>
      <version>${jersey.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

<dependencies>
  <dependency>
    <groupId>org.glassfish.jersey.containers</groupId>
    <artifactId>jersey-container-servlet-core</artifactId>
  </dependency>
  <dependency>
    <groupId>org.glassfish.hk2</groupId>
    <artifactId>hk2</artifactId>
    <version>2.3.0</version>
  </dependency>
</dependencies>

web.xml

<servlet>
    <servlet-name>Jersey Web Application</servlet-name>
    <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
    <init-param>
        <param-name>jersey.config.server.provider.packages</param-name>
        <param-value>my.package.jerseytest</param-value>
    </init-param>
    <init-param>
        <param-name>javax.ws.rs.Application</param-name>
        <param-value>my.package.jerseytest.application.Application</param-value>
    </init-param>    

    <load-on-startup>1</load-on-startup>
</servlet>

my.package.jerseytest.application.Application

public class Application extends ResourceConfig {
    public Application() {
        ServiceLocator locator = ServiceLocatorUtilities.createAndPopulateServiceLocator();
    }
}

my.package.jerseytest.service.EchoService

@Service
public class EchoService {
    public String generateResponse(String echo) {
        return echo;
    }
}

my.package.jerseytest.resource.MyResource

@Path("myresource")
public class MyResource {

    @Inject
    EchoService echoService;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String getIt() {
        return echoService.generateResponse("Got it!");
    }
}

I have checked that the inhibitant-generator does in fact run and produce its output, yet when running the Tomcat server GETting http://localhost:8080/sandbox/webapi/myresource I get

SEVERE: Servlet.service() for servlet [Jersey Web Application] in context with path [/sandbox] threw exception [A MultiException has 3 exceptions.  They are:
1. org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at SystemInjecteeImpl(requiredType=EchoService,parent=MyResource,qualifiers={},position=-1,optional=false,self=false,unqualified=null,932014249)
2. java.lang.IllegalArgumentException: While attempting to resolve the dependencies of my.package.jerseytest.resource.MyResource errors were found
3. java.lang.IllegalStateException: Unable to perform operation: resolve on my.package.jerseytest.resource.MyResource
] with root cause
org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at SystemInjecteeImpl(requiredType=EchoService,parent=MyResource,qualifiers={},position=-1,optional=false,self=false,unqualified=null,932014249)

Any ideas what I am missing? I would appreciate any help :(

NB! I know about

but they did not help me...

Community
  • 1
  • 1
Daniel David Kovacs
  • 226
  • 1
  • 2
  • 13
  • 1
    Nope, gave up. There was something about two different versions of the @Inject annotation, but the project git past this and we decided to use jersey's spring bridge and Spring DI container. – Daniel David Kovacs Dec 12 '14 at 07:59
  • Just curious, why not simply CDI? – ymajoros Aug 08 '15 at 09:28
  • Honestly, I don't think anyone thought of it. Since other parts of the project uses Spring, it was either that, or something out of the box with jersey. – Daniel David Kovacs Aug 08 '15 at 11:06
  • 1
    see http://stackoverflow.com/questions/39003933/dependency-injection-using-cdi-with-jersey-with-without-abstract-binding/40251088#40251088 – 李昭广 Nov 29 '16 at 07:15

5 Answers5

2

I'm combining the insight I gained from these two questions:

Firstly, use the HK2 Metadata Generator (or the Inhabitant Generator) in your build chain (as you do already). This will scan your source and create META-INF/hk2-locator/default.

Secondly, create a new ServiceLocator, populated with the services from the metadata:

ServiceLocator locator = ServiceLocatorUtilities.createAndPopulateServiceLocator();

Now pass it to Grizzly. Quoting @peeskillet:

Jersey has it's own ServiceLocator, and it's not easy to try a obtain a reference to it. We could give Jersey our ServiceLocator, but Jersey ultimately still creates it's own locator and will populate it with our locator.

ResourceConfig config = new MyApplicationConfig();
HttpServer server = GrizzlyHttpServerFactory.createHttpServer(
    URI.create(BASE_URI),
    config, 
    serviceLocator
);
Community
  • 1
  • 1
Hank
  • 4,597
  • 5
  • 42
  • 84
1

I solved my problem quite like this one by using a class that extends AbstractBinder, instantiating it and registering it with the application.

resourceConfig.register(new DependencyBinder());

Also,

/**
 * dependency injection bindings.
 * Jersey requires that service implementations are bound to their contracts this way.
 */
public final class DependencyBinder extends AbstractBinder {

    @Override
    protected final void configure() {
        bind(StatusServiceImpl.class).to(StatusService.class);
    }
}
Laurent Picquet
  • 1,147
  • 11
  • 12
  • 2
    Caution! Anyone using this solution should not try to make it generic. We tried to do that, and added 2 constructor parameter to this kind of binder. The result was terrible. Jersey only registered the first instance based on class type. So the first DependencyBinder would get injected, but anything after that rejected, because it's already added according to jersey. – JSONStatham Jul 31 '17 at 17:01
0

Try adding the packages that need scanned in your Application constructor. The "true" parameter on packages means to scan the package recursively:

public class Application extends ResourceConfig {
    public Application() {
        packages(true, "my.package.jerseytest");
        ServiceLocator locator = ServiceLocatorUtilities.createAndPopulateServiceLocator();
    }
}
David Fleeman
  • 2,588
  • 14
  • 17
  • Tried, sorry, I still get the same exception. Just wondering: if the names of the classes that are to be injected are collected in a file by the inhabitant-generator, then why do I need to scan packages? – Daniel David Kovacs Oct 07 '14 at 21:04
0

Use packages(true, "my.package.jerseytest"); And use org.glassfish.jersey.spi.Contract not org.jvnet.hk2.annotations.Contract annotation. And use simple interfaces without generics.

acel
  • 9
  • 1
  • Tried with and without a matching @Contract annotated interface to the service, didn't help. If I understood the documentation correctly, it is not necessary to have an interface for the service (although I do get it that from a clean code point of view it is nice to have) – Daniel David Kovacs Oct 07 '14 at 21:06
0

Try adding @Stateless

@Path("myresource")
@Stateless
public class MyResource {
    @Inject
    EchoService echoService;
...
}