question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

JAX-RS resources should have implicit Dependent annotation

See original GitHub issue

Currently I must annotate a JAX-RS resource with @Path (for JAX-RS) and @RequestScoped or @ApplicationScoped (for CDI).

As these resources are by default request scoped, maybe this should be automatically used for injection.

If I do not annotate the class with neither of Application or Request Scoped annotations, injection is going to fail in HK2

When debugging, I could see the CDI actually does inject the values correctly, yet HK2 then subsequently fails that it cannot inject them

Environment Details

  • Helidon Version: 0.10.5-SNAPSHOT
  • Helidon MP
  • JDK version: 8
  • OS: Mac

Stack trace:

WARNING: The following warnings have been detected: WARNING: Unknown HK2 failure detected:
MultiException stack 1 of 1
org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at SystemInjecteeImpl(requiredType=JsonWebToken,parent=Runner$TheResource,qualifiers={},position=-1,optional=false,self=false,unqualified=null,942306357)
	at org.jvnet.hk2.internal.ThreeThirtyResolver.resolve(ThreeThirtyResolver.java:75)
	at org.jvnet.hk2.internal.Utilities.justInject(Utilities.java:1012)
	at org.jvnet.hk2.internal.ServiceLocatorImpl.inject(ServiceLocatorImpl.java:1008)
	at org.glassfish.jersey.inject.hk2.AbstractHk2InjectionManager.inject(AbstractHk2InjectionManager.java:231)
	at org.glassfish.jersey.inject.hk2.ImmediateHk2InjectionManager.inject(ImmediateHk2InjectionManager.java:54)
	at org.glassfish.jersey.ext.cdi1x.internal.AbstractCdiBeanSupplier$2.getInstance(AbstractCdiBeanSupplier.java:112)
	at org.glassfish.jersey.ext.cdi1x.internal.AbstractCdiBeanSupplier._provide(AbstractCdiBeanSupplier.java:127)
	at org.glassfish.jersey.ext.cdi1x.internal.GenericCdiBeanSupplier.get(GenericCdiBeanSupplier.java:66)
	at org.glassfish.jersey.inject.hk2.InstanceSupplierFactoryBridge.provide(InstanceSupplierFactoryBridge.java:77)
	at org.jvnet.hk2.internal.FactoryCreator.create(FactoryCreator.java:153)
	at org.jvnet.hk2.internal.SystemDescriptor.create(SystemDescriptor.java:487)
	at org.jvnet.hk2.internal.PerLookupContext.findOrCreate(PerLookupContext.java:70)
	at org.jvnet.hk2.internal.Utilities.createService(Utilities.java:2126)
	at org.jvnet.hk2.internal.ServiceLocatorImpl.internalGetService(ServiceLocatorImpl.java:777)
	at org.jvnet.hk2.internal.ServiceLocatorImpl.internalGetService(ServiceLocatorImpl.java:740)
	at org.jvnet.hk2.internal.ServiceLocatorImpl.getService(ServiceLocatorImpl.java:710)
	at org.glassfish.jersey.inject.hk2.AbstractHk2InjectionManager.getInstance(AbstractHk2InjectionManager.java:184)
	at org.glassfish.jersey.inject.hk2.ImmediateHk2InjectionManager.getInstance(ImmediateHk2InjectionManager.java:54)
	at org.glassfish.jersey.internal.inject.Injections.getOrCreate(Injections.java:129)
	at org.glassfish.jersey.server.model.MethodHandler$ClassBasedMethodHandler.getInstance(MethodHandler.java:284)
	at org.glassfish.jersey.server.internal.routing.PushMethodHandlerRouter.apply(PushMethodHandlerRouter.java:75)
	at org.glassfish.jersey.server.internal.routing.RoutingStage._apply(RoutingStage.java:110)
	at org.glassfish.jersey.server.internal.routing.RoutingStage._apply(RoutingStage.java:113)
	at org.glassfish.jersey.server.internal.routing.RoutingStage._apply(RoutingStage.java:113)
	at org.glassfish.jersey.server.internal.routing.RoutingStage._apply(RoutingStage.java:113)
	at org.glassfish.jersey.server.internal.routing.RoutingStage.apply(RoutingStage.java:93)
	at org.glassfish.jersey.server.internal.routing.RoutingStage.apply(RoutingStage.java:62)
	at org.glassfish.jersey.process.internal.Stages.process(Stages.java:197)
	at org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:269)
	at org.glassfish.jersey.internal.Errors$1.call(Errors.java:272)
	at org.glassfish.jersey.internal.Errors$1.call(Errors.java:268)
	at org.glassfish.jersey.internal.Errors.process(Errors.java:316)
	at org.glassfish.jersey.internal.Errors.process(Errors.java:298)
	at org.glassfish.jersey.internal.Errors.process(Errors.java:268)
	at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:289)
	at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:256)
	at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:703)
	at io.helidon.webserver.jersey.JerseySupport$JerseyHandler.lambda$accept$1(JerseySupport.java:207)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:7 (7 by maintainers)

github_iconTop GitHub Comments

1reaction
ljnelsoncommented, Nov 2, 2018

Some background mostly for other readers who may come across this:

The JAX-RS specification states that a resource class’ default lifecycle is per-lookup. Section 3.1.1 reads:

By default a new resource class instance is created for each request to that resource. First the constructor (see Section 3.1.2) is called, then any requested dependencies are injected (see Section 3.2), then the appropriate method (see Section 3.3) is invoked and finally the object is made available for garbage collection.

Strictly speaking the CDI scope that matches this exactly is Dependent. However most users would probably expect RequestScoped semantics, and just as a point of note this is exactly what, for example, RestEasy does. So in general yes, treating resources that are {handwave handwave} supposed to be per-request as if they were annotated with @RequestScoped from a CDI perspective is probably the right thing to do at some abstract level.

And now a digression on JAX-RS and lifecycles (this is actually relevant! No, really!):

It is worth noting that if (again, pure JAX-RS here, no CDI, no MicroProfile) a user defines an Application such that its getSingletons() method returns a resource instance, then it is that resource instance and none other that should be used. If instead a user defines an Application such that its getClasses() method returns a resource class, then regardless of whether an instance of that class appears in the return value of getSingletons(), the lifecycle should be per-request. (If a user does not define an Application at all, then the behavior is up to the JAX-RS implementation as to what to do.)

These Application methods are the only portable ways to define lifecycles in JAX-RS; that is: JAX-RS has no notion of scope annotations at all.

Obviously JAX-RS implementations can add capabilities here and both Jersey and RestEasy do.

Jersey augments all of this by also permitting javax.inject scope annotations on resource classes. So if a resource class handled by Jersey has @Singleton on it, and if that resource class is among the set of Classes returned by Application#getClasses(), then although that normally indicates to the Application’s caller that a per-request lifecycle is desired—nevertheless @Singleton will trump here in Jersey: only one instance of that resource class will be instantiated, and it will be instantiated by Jersey (not the user or the Application), and will be dependency injected by whatever Jersey is using for dependency injection.

Next, Jersey supplies a CDI integration component, used by Helidon, and it puts CDI on top of its dependency injection facilities. So now resource classes returned from Application#getClasses() are instantiated by CDI if they are known to CDI and in the scopes that they are annotated with.

So when CDI is in the picture, as it is in MicroProfile, and therefore as it is in Helidon MicroProfile, and when you want to use behavior that is non-portable with respect to JAX-RS like auto-detecting lifecycles and/or auto-detecting resource classes and so on, you have to make CDI aware of the classes in question. (We should also double-check and make sure that Application#getSingletons() instances are shoehorned into CDI in either @ApplicationScoped or @Singleton scope; I assume the Jersey CDI support does this?)

CDI becomes aware of classes when bean discovery happens. Bean discovery broadly speaking can take place over all Java classes or only those with bean-defining annotations. (That is a vastly oversimplified summary but correct as far as it goes.) Normally you set the bean discovery mode to annotated, which means that only those classes annotated with @RequestScoped, @ApplicationScoped, @Dependent, and so on—but not, notably, @Singleton!—become known to the CDI container.

So fine: if your resource-class-that-you-want-to-be-known-to-CDI doesn’t have one of these annotations on it, then CDI doesn’t know about it. If CDI doesn’t know about it, and you’re in an environment where Jersey is in play and Jersey’s CDI support is in play (such as is the case in Helidon MicroProfile), then the behavior is going to be undefined. In this case, it looks like Jersey’s CDI support will see if CDI knows about the requested injection, which as we’ve already determined it doesn’t, and if CDI says “nope” then it goes and tries to do it itself using HK2.

The easiest way of course to solve this problem is to put an explicit bean-defining annotation on a resource class you’re planning on using with Helidon MicroProfile server, such as @Dependent or @RequestScoped. That should do the trick with a minimum of fuss. (It may even be part of the MicroProfile specification somewhere as to how to reconcile its requirement of CDI and the requirement of JAX-RS since strictly speaking by both specifications read separately this combination’s behavior is undefined. Java EE if I remember right is where, and only where, the specification of the behavior that should result from these two specifications’ co-presence is defined.)

When for whatever reason that is not an option, the next least-magic way to solve this problem is to programmatically inform the CDI container itself that it should consider the resource class as a bean in a particular scope. You can do this in a portable extension, which can use the BeforeBeanDiscovery#addAnnotatedType(Class, String) method. The returned “configurator” can be used to add the @RequestScoped or @Dependent annotation directly. Obviously you want to do this only for resource classes that are not already annotated with bean-defining annotations.

Finally you can change Helidon MicroProfile server to take any resource classes that are added to it programmatically via the Server.Builder#addResourceClass(Class) method to also add such classes into the CDI container. Currently this addition does not occur. This step is basically another formulation of the portable extension strategy spelled out one paragraph above.

0reactions
tomas-langercommented, Oct 9, 2019

This is now merged. Helidon now adds all resources annotated with @Path as beans. Default scope is @Dependent, user can define any scope desired.

Read more comments on GitHub >

github_iconTop Results From Across the Web

JAX-RS resources should have implicit Dependent annotation
Currently I must annotate a JAX-RS resource with @path (for JAX-RS) and @RequestScoped or @ApplicationScoped (for CDI).
Read more >
29.2 Creating a RESTful Root Resource Class
Root resource classes are "plain old Java objects" (POJOs) that are either annotated with @Path or have at least one method annotated with...
Read more >
jax rs - What is the best way to inject a singleton service into a ...
Declare the service class with an @ApplicationScoped annotation and inject it on your resources with @Inject . CDI will only instantiate one ...
Read more >
Chapter 3. Dependency Injection - JBoss.org
In order to inject TimeService , you must annotate it with @ApplicationScoped or the Errai DI container will not acknowledge the type as...
Read more >
Jersey 2.37 User Guide - GitHub Pages
You just need to declare (provided) dependency on JAX-RS API to be able to compile your application. 1. 2. 3. 4. 5. 6....
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found