Подтвердить что ты не робот

Использование @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 не вызывается Джерси. Что я здесь делаю неправильно?

4b9b3361

Ответ 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.