Использование @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, но я не проверял его в других настройках.

Для меня преимущество над решением Брайанта состояло в том, что мне не нужно писать какой-то класс резолвера только для того, чтобы я мог получить доступ к своим синглетам в области приложения, таким как база данных.

Другие вопросы по тегам