Использование @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
не вызывается Джерси. Что я здесь делаю неправильно?
Ответы
Ответ 1
Я не думаю, что существует 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, чтобы помочь вам здесь.
Ответ 2
Внедрите InjectableProvider. Скорее всего, путем расширения PerRequestTypeInjectableProvider или SingletonTypeInjectableProvider.
@Provider
public class StorageEngineResolver extends SingletonTypeInjectableProvider<Context, StorageEngine>{
public MyContextResolver() {
super(StorageEngine.class, new InMemoryStorageEngine());
}
}
Позволит вам:
@Context StorageEngine storage;
Ответ 3
Я нашел другой путь. В моем случае я хочу предоставить пользователю, который в настоящее время зарегистрирован как пользовательский объект из моего уровня persitence.
Это класс:
@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-Rs и получил SecurityContext
.
Чтобы получить текущего пользователя, я использую CDI с моим Qualifier @CurrentUser
. Поэтому в каждом месте, где мне нужен текущий пользователь i, введите:
@Inject
@CurrentUser
private User user;
И действительно
@Context
private User user;
не работает (пользователь равен нулю).
Ответ 4
Образец, который работает для меня: добавьте несколько полей в подкласс приложения, которые предоставляют объекты, которые нужно ввести. Затем используйте абстрактный базовый класс для "инъекции":
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, но я не тестировал его в других настройках.
Для меня преимущество перед решением Брайанта заключалось в том, что мне не нужно писать какой-то класс resolver, чтобы я мог получить в своих синглонах с областью приложения, подобной базе данных.
Ответ 5
Если кто-то использует Resteasy, это то, что работает для меня.
Если вы добавите что-то вроде этого:
ResteasyContext.pushContext(StorageEngine.class, new StorageEngine());
в нечто вроде фильтра Jaxrs, он позволяет вам сделать что-то вроде этого:
@GET
@Path("/some/path")
public Response someMethod(@Context StorageEngine myStorageEngine) {
...
}
Это характерно для Resteasy, который не имеет что-то вроде SingletonTypeInjectableProvider
.