Использование @Context, @Provider и ContextResolver в JAX-RS
Я только знакомлюсь с реализацией веб-сервисов REST в Java с использованием JAX-RS и столкнулся со следующей проблемой. Один из моих классов ресурсов требует доступа к серверной части хранилища, которая абстрагируется за 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
Тем не менее, значение storage
в моем классе ресурсов всегда null
- ни конструктор StorageEngineProvider
ни его getContext
метод вызывается Джерси, когда-либо. Что я здесь не так делаю?
4 ответа
Я не думаю, что есть 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 Context и Dependency Injection (JSR-299) (которые должны быть доступны в Java EE 6) или другие структуры внедрения зависимостей, такие как Google Guice, чтобы помочь вам здесь.
Реализуйте InjectableProvider. Скорее всего, путем расширения PerRequestTypeInjectableProvider или SingletonTypeInjectableProvider.
@Provider
public class StorageEngineResolver extends SingletonTypeInjectableProvider<Context, StorageEngine>{
public MyContextResolver() {
super(StorageEngine.class, new InMemoryStorageEngine());
}
}
Позвольте вам иметь:
@Context StorageEngine storage;
Я нашел другой способ. В моем случае я хочу предоставить пользователю, который в данный момент вошел в систему как пользовательский объект из моего уровня постоянства. Это класс:
@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<User>
а также @Provider
чтобы получить этот класс, обнаруженный Jax-R и получить SecurityContext
впрыскивается. Чтобы получить текущего пользователя, я использую CDI с моим классификатором @CurrentUser
, Поэтому в каждом месте, где мне нужен текущий пользователь, я печатаю:
@Inject
@CurrentUser
private User user;
И действительно
@Context
private User user;
не работает (пользователь нулевой).
Если кто-то использует Resteasy, это то, что сработало для меня.
Если вы добавите что-то вроде этого:
ResteasyContext.pushContext(StorageEngine.class, new StorageEngine());
во что-то вроде фильтра jaxrs, он позволяет вам делать что-то вроде этого:
@GET
@Path("/some/path")
public Response someMethod(@Context StorageEngine myStorageEngine) {
...
}
Это характерно для Resteasy, в котором нет чего-то вроде SingletonTypeInjectableProvider
.
Шаблон, который работает для меня: добавьте несколько полей в ваш подкласс Application, которые предоставляют объекты, которые вам нужно внедрить. Затем используйте абстрактный базовый класс, чтобы выполнить "инъекцию":
public abstract class ServiceBase {
protected Database database;
@Context
public void setApplication(Application app) {
YourApplication application = (YourApplication) app;
database = application.getDatabase();
}
}
Все ваши сервисы, которым требуется доступ к базе данных, теперь могут расширять ServiceBase и автоматически предоставлять базу данных через защищенное поле (или, если хотите, получатель).
Это работает для меня с Undertow и Resteasy. Теоретически это должно работать во всех реализациях JAX-RS, поскольку внедрение приложения поддерживается стандартным AFAICS, но я не проверял его в других настройках.
Для меня преимущество над решением Брайанта состояло в том, что мне не нужно писать какой-то класс резолвера только для того, чтобы я мог получить доступ к своим синглетам в области приложения, таким как база данных.