What Objects How to Inject Using the @Context Annotation

What objects can I inject using the @Context annotation?

The riveting JAX-RS specification defines all the standard types you can inject via @Context.

But if I were you, I would just consult the specific documentation of your chosen provider to see what is available.

For example, RESTEasy provides these values via @Context. Meanwhile, Jersey provides these. Obviously there will be overlap because of the standard context values.

Where do @Context objects come from

You can write your own injection provider and plug that into Jersey - look at SingletonTypeInjectableProvider and PerRequestTypeInjectableProvider - extend one of these classes (depending on the lifecycle you want for the injectable object) and register your implementation as a provider in your web app.

For example, something like this:

@Provider
public class MyObjectProvider extends SingletonTypeInjectableProvider<Context, MyObject> {
public MyObjectProvider() {
// binds MyObject.class to a single MyObject instance
// i.e. the instance of MyObject created bellow will be injected if you use
// @Context MyObject myObject
super(MyObject.class, new MyObject());
}
}

To include the provider in your web app you have several options:

  1. if your app uses classpath scanning (or package scanning) just make sure the provider is in the right package / on the classpath
  2. or you can simply register it using META-INF/services entry (add META-INF/services/com.sun.jersey.spi.inject.InjectableProvider file having the name of your provider class in it's contents)

How to inject application context in a repository with Hilt?

Just use @ApplicationContext annotation on your context parameter.

By annotating context with @ApplicationContext provided by
Hilt, we don't need to create a provider for the application context.

import dagger.hilt.android.qualifiers.ApplicationContext

/* For hilt versions lower than v2.28.2 use ApplicationComponent instead of
SingletonComponent. ApplicationComponent is deprecated and even removed in
some versions above v2.28.2 so better refactor it to SingletonComponent. */

@Module
@InstallIn(SingletonComponent::class)
class ProductionModule {

@Singleton
@Provides
fun provideAppDatabase(@ApplicationContext appContext: Context): AppDatabase {
return Room
.databaseBuilder(appContext, AppDatabase::class.java, AppDatabase.DB_NAME)
.build()
}
}

NOTE: In case you are tempted to pass activity context as a dependency, try to use application context or rethink your use-case. Passing activity context may lead to serious implications like memory leaks. Having said that, if you know what you are doing, use @ActivityContext annotation for passing the activity context. A possible use-case might be adapters.

How to set an object to context so that i can get it anywhere in the application with @Context

I had some time and wrote up the way to do it (jersey only, no other DI framework used).

Jersey is compliant with javax.inject annotations. The reason you do NOT use a context annotation is because (by the sound of it) your MyObject class is not a context object (e.g. it doesn't change with each request like e.g. HttpServletRequest which is injectable). So we need to bind your object.

Consider my implementation of MyObject:

public class MyObject {

String get() {
return "I am an object";
}
}

This object needs to be available in my jersey classes (resource, filter etc). I wrote a little resource using this bean:

@Path("context")
public class ContextResource {

@Inject
MyObject o;

@GET
public String get() {
return o.get();
}

}

Note that I am using the javax.inject.Inject annotation for this case to tell jersey I want this particular bean injected.
All I need to do now is to tell jersey about this bean. In my DW application I do:

public class Application extends io.dropwizard.Application<Configuration>{  

@Override
public void run(Configuration configuration, Environment environment) throws Exception {

environment.jersey().register(ContextResource.class);
environment.jersey().register(new AbstractBinder() {

@Override
protected void configure() {
bind(MyObject.class).to(MyObject.class);
}
});
}

public static void main(String[] args) throws Exception {
new Application().run("server", "/home/artur/dev/repo/sandbox/src/main/resources/config/test.yaml");
}
}

Note that I am using a binder to bind my bean. The syntax looks funky, but essentially it is doing a "bind the type to the implementation". Since my type is my implementation (I am not using an interface for MyObject), this looks like:

bind(MyObject.class).to(MyObject.class) 

Now jersey knows about my bean and will happily inject it.

Running all my code prints:

artur@pandaadb:~/dev/vpn$ curl localhost:9085/api/context
I am an object

Hope that brings some insights on how to use injection without a framework. Personally I would recommend using guice with dropwizard (google: dropwizard-guicey) which makes these kind of things very easy.

Regards,

Artur

How to inject an object into jersey request context?

You could just use ContainterRequestContext.setProperty(String, Object). Then just inject the ContainerRequestContext

@Override
public void filter(ContainerRequestContext crc) throws IOException {
MyObject obj = new MyObject();
crc.setProperty("myObject", myObject);
}

@POST
public Response getResponse(@Context ContainerRequestContext crc) {
return Response.ok(crc.getProperty("myObject")).build();
}

Another option to inject the MyObject directly is to use the HK2 functionality Jersey 2 offers.

Create a factory the inject the ContainerRequestContext and return the MyObject. For example

import javax.inject.Inject;
import javax.ws.rs.container.ContainerRequestContext;
import jetty.plugin.test.domain.MyObject;
import org.glassfish.hk2.api.Factory;

public class MyObjectFactory implements Factory<MyObject> {

private final ContainerRequestContext context;

@Inject
public MyObjectFactory(ContainerRequestContext context) {
this.context = context;
}

@Override
public MyObject provide() {
return (MyObject)context.getProperty("myObject");
}

@Override
public void dispose(MyObject t) {}
}

You then need to bind the factory:

public class InjectApplication extends ResourceConfig {

public InjectApplication() {
...
register(new AbstractBinder(){
@Override
protected void configure() {
bindFactory(MyObjectFactory.class)
.to(MyObject.class)
.in(RequestScoped.class);
}
});
}
}

With the same setting of the property as in the filter example above, you can then just inject the MyObject with the @Context

@GET
public Response getTest(@Context MyObject myObject) {
return Response.ok(myObject.getMessage()).build();
}


  • See more at Custom Injection


UPDATE

Please see this question for a problem with this implementation.

See Also:

  • If you're using web.xml instead of a ResourceConfig

jersey 2 context injection based upon HttpRequest without singleton

So when you instantiate the resource to make it a singleton, Jersey tries to do all the injection on startup. This means that attempt to access any object that is inherently request scoped, will fail... UNLESS... the object is proxiable.

Some objects are made proxiable by Jersey, and this is by design, and by specification. For example HttpHeaders, UriInfo, SecurityContext, and a few others listed here. Though HttpServletRequest is not listed, it is also one of the objects that are proxiable.

What it means to be proxiable is that instead of injecting the actual object (which doesn't exist until there is request), a proxy is injected. When calls are made on the proxy, they get forwarded to the actual object that is available in the current request. You can try to print/log the class of the HttpServletRequest and you will see that the class is actually com.sun.proxy.ProxyX instead of HttpServletRequestSomeImpl. This is the magic of Java's dynamic proxies at work.

The current problem you are facing is the injection of Datastore. It is inherently request scoped because it's creation in dependent on request context information, i.e. the headers. So during injection, it fails on this call to obtain the ContainerRequest inside your factory

ContainerRequest request = getContainerRequest();

The error message being "Not inside a request scope", which makes perfect sense as there is no request when we try to obtain it.

So how can we fix this? Well we need to make the Datastore proxiable.
Generally, the way you can do this is by configuring it during binding declaration, for example

bindFactory(...).proxy(true).proxyForSameScope(false).to(...);

The proxy(true) method makes it proxiable, and the proxyForSameScope(false) says that if we are trying to inject into the same scope, it should not be a proxy, but the actual instance.

One problem with your current configuration is that you are binding the factory to the factory

bind(TenantDatastoreFactory.class)
.to(TenantDatastoreFactory.class)
.in(Singleton.class);

This makes sense for your current implementation, as you are trying to inject the factory into the TenantDatastoreFactoryProvider. But what we actually need to make the proxying work, is for the factory to be binded to the actual Datastore:

bindFactory(TenantDatastoreFactory.class)
.proxy(true)
.proxyForSameScope(false)
.to(Datastore.class)
.in(RequestScoped.class);

So now we have taken out the binding of the factory, we can't inject it. So we just have to problem of returning a Factory from the createValueFactory method. We don't want to just return TenantDatastoreFactory instance because we'll still face the same problem where the provide method is called to get the Datastore. To get around this, we can do the following

@Override
protected Factory<?> createValueFactory(Parameter parameter) {
Class<?> paramType = parameter.getRawType();
TenantDatastore annotation = parameter.getAnnotation(TenantDatastore.class);
if (annotation != null && paramType.isAssignableFrom(Datastore.class)) {
return getFactory();
}
return null;
}

private Factory<Object> getFactory() {
return new Factory<Object>() {

@Context
Datastore datastore;

@Override
public Object provide() {
return datastore;
}

@Override
public void dispose(Object t) {}
};
}

So we are creating a Factory dynamically, where we inject the proxied Datastore. Now when Jersey tries to inject the resource class, it will inject the proxy, and the provide method is never called on start up. It is only called when we try o actually use the Datastore during the request.

It may seem redundant, that we have both the TenantDatastoreFactory and the anonymous Factory created as runtime. But this is necessary to make the Datastore proxiable and make sure the provide() method is never called on startup.

Another note, is that if you don't require parameter injection, you could've simplified the implementation by taking out the TenantDatastoreFactoryProvider. This is only required for parameter injection. All we need is the InjectionResolver to handle the custom annotation, and the factory to create the Datastore. The InjectionResolver implementation would need to be changed as follows

public class TenantDatastoreInjectionResolver 
implements InjectionResolver<TenantDatastore> {

@Inject
@Named(InjectionResolver.SYSTEM_RESOLVER_NAME)
InjectionResolver<Inject> systemInjectionResolver;

@Override
public Object resolve(Injectee injectee, ServiceHandle<?> handle) {
if (Datastore.class == injectee.getRequiredType()) {
return systemInjectionResolver.resolve(injectee, handle);
}
return null;
}

@Override
public boolean isConstructorParameterIndicator() { return false; }
@Override
public boolean isMethodParameterIndicator() { return false; }
}

Then in the binder, just take out the TenantDatastoreFactoryProvider

@Override
public void configure() {
bindFactory(TenantDatastoreFactory.class)
.proxy(true)
.proxyForSameScope(false)
.to(Datastore.class)
.in(RequestScoped.class);
bind(TenantDatastoreInjectionResolver.class)
.to(new TypeLiteral<InjectionResolver<TenantDatastore>>() {
})
.in(Singleton.class);
}

Again this is only if you don't require parameter injection.

See Also

  • Injecting Request Scoped Objects into Singleton Scoped Object with HK2 and Jersey


Related Topics



Leave a reply



Submit