开发者

Using @Context, @Provider and ContextResolver in JAX-RS

I'm just getting acquainted with implementing REST web services in Java using JAX-RS and I ran into the following problem. One of my resource classes requires access to a storage backend, which is abstracted away behind a StorageEngine interface. I would like to inject the current StorageEngine instance into the resource class serving the REST requests and I thought a nice way of doing this would be by using the @Context annotation and an appropriate ContextResolver class. This is what I have so far:

In MyResource.java:

class MyResource {
    @Context StorageEngine storage;
    [...]
}

In StorageEngineProvider.java:

@Provider
class StorageEngineProvider implements ContextResolver<StorageEngine> {
    private StorageEngine storage = new InMemoryStorageEngine();

    public StorageEngine getContext(Class<?> type) {
        if (type.equals(StorageEngine.class))
            return storage;
        return null;
    }
}

I'm using com.sun.jersey.api.core.PackagesResourceConfig to discover the providers and the resource classes automatically, and according to the logs, it picks up the StorageEngineProvider class nicely (timestamps and unnecessary stuff left out intentionally):

INFO: Root resou开发者_StackOverflow社区rce classes found:
    class MyResource
INFO: Provider classes found:
    class StorageEngineProvider

However, the value of storage in my resource class is always null - neither the constructor of StorageEngineProvider nor its getContext method is called by Jersey, ever. What am I doing wrong here?


I don't think there's a JAX-RS specific way to do what you want. The closest would be to do:

@Path("/something/")
class MyResource {
    @Context
    javax.ws.rs.ext.Providers providers;

    @GET
    public Response get() {
        ContextResolver<StorageEngine> resolver = providers.getContextResolver(StorageEngine.class, MediaType.WILDCARD_TYPE);
        StorageEngine engine = resolver.get(StorageEngine.class);
        ...
    }
}

However, I think the @javax.ws.rs.core.Context annotation and javax.ws.rs.ext.ContextResolver is really for types related to JAX-RS and supporting JAX-RS providers.

You may want to look for Java Context and Dependency Injection (JSR-299) implementations (which should be available in Java EE 6) or other dependency injection frameworks such as Google Guice to help you here.


Implement a InjectableProvider. Most likely by extending PerRequestTypeInjectableProvider or SingletonTypeInjectableProvider.

@Provider
public class StorageEngineResolver extends SingletonTypeInjectableProvider<Context, StorageEngine>{
    public MyContextResolver() {
        super(StorageEngine.class, new InMemoryStorageEngine());
    }
}

Would let you have:

@Context StorageEngine storage;


I found another way. In my case i want to provide the user currently logged in as a User entity from my persitence layer. This is the class:

@RequestScoped
@Provider
public class CurrentUserProducer implements Serializable, ContextResolver<User> {

    /**
     * Default
     */
    private static final long serialVersionUID = 1L;


    @Context
    private SecurityContext secContext;

    @Inject
    private UserUtil userUtil;

    /**
     * Tries to find logged in user in user db (by name) and returns it. If not
     * found a new user with role {@link UserRole#USER} is created.
     * 
     * @return found user or a new user with role user
     */
    @Produces
    @CurrentUser
    public User getCurrentUser() {
        if (secContext == null) {
            throw new IllegalStateException("Can't inject security context - security context is null.");
        }
        return userUtil.getCreateUser(secContext.getUserPrincipal().getName(),
                                      secContext.isUserInRole(UserRole.ADMIN.name()));
    }

    @Override
    public User getContext(Class<?> type) {
        if (type.equals(User.class)) {
            return getCurrentUser();
        }
        return null;
    }

}

I only used implements ContextResolver<User> and @Provider to get this class discovered by Jax-Rs and get SecurityContext injected. To get the current user i use CDI with my Qualifier @CurrentUser. So on every place where i need the current user i type:

@Inject
@CurrentUser
private User user;

And indeed

@Context
private User user;

does not work (user is null).


If anyone is using Resteasy this is what worked for me.

If you add something like this:

ResteasyContext.pushContext(StorageEngine.class, new StorageEngine());

into something like a jaxrs filter, it allows you to do something like this:

@GET
@Path("/some/path")
public Response someMethod(@Context StorageEngine myStorageEngine) {
 ...
}

This is specific to Resteasy, which doesn't have something like SingletonTypeInjectableProvider.


A pattern that works for me: Add some fields on your Application subclass that provide the objects you need to inject. Then use an abstract base class to do the "injection":

public abstract class ServiceBase {

    protected Database database;

    @Context
    public void setApplication(Application app) {
        YourApplication application = (YourApplication) app;
        database = application.getDatabase();
    }
}

All your services that need to access the database may now extend ServiceBase and have the database available automatically via the protected field (or a getter, if you prefer that).

This works for me with Undertow and Resteasy. In theory this should work across all JAX-RS implementations since injection of the Application is supported by the standard AFAICS, but I haven't tested it in other settings.

For me, the advantage over Bryant's solution was that I don't have to write some resolver class just so I can get at my application-scoped singletons like the database.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜