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

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

MyResource.java

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

StorageEngineProvider.java

 @Provider class StorageEngineProvider implements ContextResolver { 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 

但是,我的资源类中的storage值始终为null – 从来没有使用Jersey调用StorageEngineProvider的构造函数或其getContext方法。 我在这做错了什么?

我不认为有一种JAX-RS特定的方式可以做你想要的。 最接近的是:

 @Path("/something/") class MyResource { @Context javax.ws.rs.ext.Providers providers; @GET public Response get() { ContextResolver 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上下文和dependency injection(JSR-299)实现(应该在Java EE 6中可用)或其他dependency injection框架(如Google Guice)来帮助您。

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

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

让你有:

 @Context StorageEngine storage; 

我找到了另一种方式。 在我的情况下,我想提供当前从我的persitence层登录为用户实体的用户。 这是class级:

 @RequestScoped @Provider public class CurrentUserProducer implements Serializable, ContextResolver { /** * 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 implements ContextResolver@Provider来获取Jax-Rs发现的这个类,并注入SecurityContext 。 为了获得当前用户,我使用CDI和我的Qualifier @CurrentUser 。 所以在我需要当前用户的每个地方我输入:

 @Inject @CurrentUser private User user; 

确实如此

 @Context private User user; 

不起作用(用户为空)。

适合我的模式:在Application子类上添加一些提供需要注入的对象的字段。 然后使用抽象基类来执行“注入”:

 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的解决方案的优势在于我不必编写一些解析器类,因此我可以使用像数据库这样的应用程序范围的单例。