在 JAX-RS 中使用 @Context、@Provider 和 ContextResolver

发布于 2024-09-05 14:43:57 字数 1205 浏览 4 评论 0原文

我刚刚熟悉使用 JAX-RS 在 Java 中实现 REST Web 服务,但遇到了以下问题。我的一个资源类需要访问存储后端,该后端被抽象在 StorageEngine 接口后面。我想将当前的 StorageEngine 实例注入到服务 REST 请求的资源类中,我认为一个很好的方法是使用 @Context 注释和适当的 ContextResolver 类。这是我到目前为止所拥有的:

MyResource.java 中:

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

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;
    }
}

我正在使用 com.sun.jersey.api.core.PackagesResourceConfig 自动发现提供程序和资源类,并根据日志,它很好地选择了 StorageEngineProvider 类(故意省略了时间戳和不必要的内容):

INFO: Root resource classes found:
    class MyResource
INFO: Provider classes found:
    class StorageEngineProvider

但是, <我的资源类中的 code>storage 始终为 null - Jersey 都不会调用 StorageEngineProvider 的构造函数及其 getContext 方法,曾经。我在这里做错了什么?

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 resource 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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

浮萍、无处依 2024-09-12 14:43:57

我认为没有 JAX-RS 特定的方法可以完成您想要的操作。最接近的是:

@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);
        ...
    }
}

但是,我认为 @javax.ws.rs.core.Context 注释和 javax.ws.rs.ext.ContextResolver 实际上适用于与 JAX-RS 相关的类型和支持 JAX-RS 提供程序。

您可能需要寻找 Java 上下文和依赖注入 (JSR-299) 实现(应该在 Java EE 6 中可用)或其他依赖注入框架(例如 Google Guice)来为您提供帮助。

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.

后来的我们 2024-09-12 14:43:57

实现 InjectableProvider 。最有可能的是通过扩展 PerRequestTypeInjectableProvider 或 SingletonTypeInjectableProvider。

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

会让你拥有:

@Context StorageEngine storage;

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;
寂寞花火° 2024-09-12 14:43:57

我找到了另一种方法。就我而言,我想从我的持久层提供当前作为用户实体登录的用户。
这是这个类:

@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;
    }

}

我只使用 implements ContextResolver@Provider 来让 Jax-Rs 发现这个类并注入 SecurityContext
为了获取当前用户,我将 CDI 与我的限定符 @CurrentUser 结合使用。因此,在我需要当前用户的每个地方,我都会输入:

@Inject
@CurrentUser
private User user;

确实

@Context
private User user;

不起作用(用户为空)。

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).

凶凌 2024-09-12 14:43:57

如果有人使用 Resteasy,这对我有用。

如果您将这样的内容添加到

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

诸如 jaxrs 过滤器之类的内容中,它允许您执行以下操作:

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

这是特定于 Resteasy 的,它没有像 SingletonTypeInjectableProvider 这样的内容。

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.

梅倚清风 2024-09-12 14:43:57

对我有用的模式:在您的应用程序子类上添加一些字段,以提供您需要注入的对象。然后使用抽象基类进行“注入”:

public abstract class ServiceBase {

    protected Database database;

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

所有需要访问数据库的服务现在都可以扩展 ServiceBase 并通过受保护的字段(或 getter,如果您愿意的话)自动使用数据库。

这对我来说适用于 Undertow 和 Resteasy。理论上,这应该适用于所有 JAX-RS 实现,因为标准 AFAICS 支持应用程序的注入,但我尚未在其他设置中对其进行测试。

对我来说,相对于 Bryant 解决方案的优势在于,我不必编写一些解析器类,这样我就可以获取应用程序范围的单例(例如数据库)。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文