Джерси: InjectableProvider не поднял - Spring

В настоящее время я пытаюсь создать InjectableProvider с Джерси, но я не могу заставить Джерси забрать его.

Я не могу найти реальных примеров его использования или даже как его получить, кроме использования аннотации @Provider для реализации. Человек, который, казалось бы, написал его в Джерси, подразумевал в некоторых сообщениях, что этого достаточно, чтобы его поднять.

Нужно ли мне указывать какой-либо служебный файл SPI или добавлять его к некоторому factory где-нибудь?

Примечание. Я работаю в Glassfish 3.1 и используя Spring 3.1. Кажется разумным, что Spring может каким-то образом взять на себя автоматическую загрузку Provider s. Однако я просто не знаю. Я вообще не использую Spring для управления предложенным InjectableProvider ниже, и я не пытаюсь добавить его каким-либо другим способом, что может быть моей проблемой.

import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.PerRequestTypeInjectableProvider;

public abstract class AbstractAttributeInjectableProvider<T>
        extends PerRequestTypeInjectableProvider<AttributeParam, T>
{
    protected final Class<T> type;

    public AbstractAttributeInjectableProvider(Class<T> type)
    {
        super(type);

        this.type = type;
    }

    @Override
    public Injectable<T> getInjectable(ComponentContext componentContext,
                                       AttributeParam attributeParam)
    {
        return new AttributeInjectable<T>(type, attributeParam.value());
    }
}

Основная реализация:

import javax.ws.rs.ext.Provider;

@Component // <- Spring Annotation
@Provider  // <- Jersey Annotation
public class MyTypeAttributeInjectableProvider
        extends AbstractAttributeInjectableProvider<MyType>
{
    public MyTypeAttributeInjectableProvider()
    {
        super(MyType.class);
    }
}

Ссылка Annotation:

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AttributeParam
{
    /**
     * The value is the name to request as an attribute from an {@link
     * HttpContext} {@link HttpServletRequest}.
     * @return Never {@code null}. Should never be blank.
     */
    String value();
}

Ссылка ссылка от разработчика в Джерси.


ОБНОВЛЕНИЕ: calvinkrishy указал на два недостатка в моем мышлении.

Во-первых, я предположил, что Джерси начнет сканирование на @Provider после того, как его отправит традиционный твитер Spring: com.sun.jersey.spi.spring.container.servlet.SpringServlet. Это было в основном неправильным; он начинает сканирование, но он ищет Spring beans, у которого есть аннотация.

Во-вторых, я предположил, что PerRequestTypeInjectableProvider будет запрашивать каждый входящий запрос для Injectable для обработки аннотации, которую он контролирует. Это тоже было неправильно. PerRequestTypeInjectableProvider создается при запуске, как и ожидалось, но Джерси затем немедленно просит Injectable обрабатывать данную аннотацию с данным type, которую он определяет, сканируя Restful Services, которые у нее есть, - на этом этапе - - решил, что он управляет (то есть, все они).

Разница между PerRequestTypeInjectableProvider и SingletonTypeInjectableProvider, по-видимому, заключается в том, что в полученном Injectable есть значение, не работающее для него (singleton), или каждый раз он просматривает значение (для каждого запроса) что позволяет изменять значение для каждого запроса.

Это бросило в мои планы меньший ключ, заставив меня сделать некоторую дополнительную работу в моем AttributeInjectable (код ниже), а не передавать некоторые объекты, как я и планировал, чтобы не давать дополнительные знания AttributeInjectable.

public class AttributeInjectable<T> implements Injectable<T>
{
    /**
     * The type of data that is being requested.
     */
    private final Class<T> type;
    /**
     * The name to extract from the {@link HttpServletRequest} attributes.
     */
    private final String name;

    /**
     * Converts the attribute with the given {@code name} into the {@code type}.
     * @param type The type of data being retrieved
     * @param name The name being retrieved.
     * @throws IllegalArgumentException if any parameter is {@code null}.
     */
    public AttributeInjectable(Class<T> type, String name)
    {
        // check for null

        // required
        this.type = type;
        this.name = name;
    }

    /**
     * Look up the requested value.
     * @return {@code null} if the attribute does not exist or if it is not the
     *         appropriate {@link Class type}.
     *         <p />
     *         Note: Jersey most likely will fail if the value is {@code null}.
     * @throws NullPointerException if {@link HttpServletRequest} is unset.
     * @see #getRequest()
     */
    @Override
    public T getValue()
    {
        T value = null;
        Object object = getRequest().getAttribute(name);

        if (type.isInstance(object))
        {
            value = type.cast(object);
        }

        return value;
    }

    /**
     * Get the current {@link HttpServletRequest} [hopefully] being made
     * containing the {@link HttpServletRequest#getAttribute(String) attribute}.
     * @throws NullPointerException if the Servlet Filter for the {@link
     *                              RequestContextHolder} is not setup
     *                              appropriately.
     * @see org.springframework.web.filter.RequestContextFilter
     */
    protected HttpServletRequest getRequest()
    {
        // get the request from the Spring Context Holder (this is done for
        //  every request by a filter)
        ServletRequestAttributes attributes =
            (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();

        return attributes.getRequest();
    }
}

Я надеялся пройти в HttpServletRequest из Provider, но AttributeInjectable создается только для уникальной аннотации/типа. Поскольку я не могу этого сделать, я делаю это для поиска по значениям, который использует Spring RequestContextFilter singleton, который предоставляет механизм ThreadLocal для безопасного извлечения HttpServletRequest (помимо прочего, связанных с текущим запросом).

<filter>
    <filter-name>requestContextFilter</filter-name>
    <filter-class>
        org.springframework.web.filter.RequestContextFilter
    </filter-class>
</filter>
<filter-mapping>
    <filter-name>requestContextFilter</filter-name>
    <url-pattern>/path/that/i/wanted/*</url-pattern>
</filter-mapping>

Результат работает, и он делает код более читабельным, не заставляя различные службы расширять базовый класс, чтобы скрыть использование @Context HttpServletRequest request, которое затем используется для доступа к атрибутам, как это сделано выше, с помощью некоторого вспомогательного метода.

Затем вы можете сделать что-то по строкам:

@Path("my/path/to")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.TEXT_PLAIN)
public interface MyService
{
    @Path("service1")
    @POST
    Response postData(@AttributeParam("some.name") MyType data);

    @Path("service2")
    @POST
    Response postOtherData(@AttributeParam("other.name") MyOtherType data);
}

@Component // Spring
public class MyServiceBean implements MyService
{
    @Override
    public Response postData(MyType data)
    {
        // interact with data
    }

    @Override
    public Response postOtherData(MyOtherType data)
    {
        // interact with data
    }
}

Это становится очень удобным, поскольку я использую Servlet Filter, чтобы гарантировать, что у пользователя есть соответствующие права доступа к службе перед передачей данных, а затем я могу анализировать входящие данные (или загружать их или что-то еще) и выгружать их в атрибут для загрузки.

Если вам не нужен описанный выше подход Provider, и вы хотите получить базовый класс для доступа к атрибутам, то здесь вы идете:

public class RequestContextBean
{
    /**
     * The current request from the user.
     */
    @Context
    protected HttpServletRequest request;

    /**
     * Get the attribute associated with the current {@link HttpServletRequest}.
     * @param name The attribute name.
     * @param type The expected type of the attribute.
     * @return {@code null} if the attribute does not exist, or if it does not
     *         match the {@code type}. Otherwise the appropriately casted
     *         attribute.
     * @throws NullPointerException if {@code type} is {@code null}.
     */
    public <T> T getAttribute(String name, Class<T> type)
    {
        T value = null;
        Object attribute = request.getAttribute(name);

        if (type.isInstance(attribute))
        {
            value = type.cast(attribute);
        }

        return value;
    }
}

@Path("my/path/to")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.TEXT_PLAIN)
public interface MyService
{
    @Path("service1")
    @POST
    Response postData();

    @Path("service2")
    @POST
    Response postOtherData();
}

@Component
public class MyServiceBean extends RequestContextBean implements MyService
{
    @Override
    public Response postData()
    {
        MyType data = getAttribute("some.name", MyType.class);
        // interact with data
    }

    @Override
    Response postOtherData()
    {
        MyOtherType data = getAttribute("other.name", MyOtherType.class);
        // interact with data
    }
}

UPDATE2. Я думал о моей реализации AbstractAttributeInjectableProvider, который сам по себе является общим классом, который существует только для предоставления AttributeInjectable для данного типа, Class<T> и поставляемого AttributeParam, Гораздо проще обеспечить реализацию не abstract, которая сообщает свой тип (Class<T>) с каждым запрошенным AttributeParam, тем самым избегая кучки реализаций только для конструкторов, предоставляющих вам тип. Это также позволяет избежать необходимости писать код для каждого отдельного типа, который вы хотите использовать с аннотацией AttributeParam.

@Component
@Provider
public class AttributeParamInjectableProvider
        implements InjectableProvider<AttributeParam, Type>
{
    /**
     * {@inheritDoc}
     * @return Always {@link ComponentScope#PerRequest}.
     */
    @Override
    public ComponentScope getScope()
    {
        return ComponentScope.PerRequest;
    }

    /**
     * Get an {@link AttributeInjectable} to inject the {@code parameter} for
     * the given {@code type}.
     * @param context Unused.
     * @param parameter The requested parameter
     * @param type The type of data to be returned.
     * @return {@code null} if {@code type} is not a {@link Class}. Otherwise
     *         an {@link AttributeInjectable}.
     */
    @Override
    public AttributeInjectable<?> getInjectable(ComponentContext context,
                                                AttributeParam parameter,
                                                Type type)
    {
        AttributeInjectable<?> injectable = null;

        // as long as it something that we can work with...
        if (type instanceof Class)
        {
            injectable = getInjectable((Class<?>)type, parameter);
        }

        return injectable;
    }

    /**
     * Create a new {@link AttributeInjectable} for the given {@code type} and
     * {@code parameter}.
     * <p />
     * This is provided to avoid the support for generics without the need for
     * {@code SuppressWarnings} (avoided via indirection).
     * @param type The type of data to be returned.
     * @param parameter The requested parameter
     * @param <T> The type of data being accessed by the {@code param}.
     * @return Never {@code null}.
     */
    protected <T> AttributeInjectable<T> getInjectable(Class<T> type,
                                                       AttributeParam parameter)
    {
        return new AttributeInjectable<T>(type, parameter.value());
    }
}

Примечание: каждый Injectable создается один раз при запуске, а не в запросе, но они вызывается при каждом входящем запросе.

Ответ 1

Как вы инициализируете Джерси?

Я предполагаю, что вы используете Джерси, используя свиток-джерси - spring. В этом случае Джерси по умолчанию будет инициализировать с помощью Spring beans, и поэтому ваш Provider должен быть Spring bean. Попробуйте добавить @Named (или если вы не используете atinject @Component или одну из аннотаций Spring) для своего Provider.

Пример использования инъецируемых поставщиков.


Обновлено. Более ясность в отношении объема инъекции:

Provider должен быть Singleton, как и для всех практических целей, a factory с привязкой к нему, и нет необходимости создавать factory для каждого запроса. Само по себе инъекция будет выполняться по запросу. Другими словами, метод getInjectable будет вызываться для каждого запроса. У вас была возможность попробовать это?

OTOH, если вы расширите SingletonTypeInjectableProvider, каждый раз один и тот же объект будет вноситься в ваш ресурс.

Я не уверен, что полностью понимаю вашу реализацию Provider. Я считаю, что что-то вроде следующего должно работать.

public class UserProvider extends PerRequestTypeInjectableProvider<AttributeParam, Users>{

    public UserProvider(){
        super(Users.class);
    }

    @Context
    HttpServletRequest request;

    @Override
    public Injectable<Users> getInjectable(ComponentContext cc, AttributeParam a) {

        String attributeValue = AnnotationUtils.getValue(a);

        return new Injectable<Users>(){

            public Users getValue() {
                System.out.println("Called"); //This should be called for each request
                return request.getAttribute(attributeValue);
            }

        };

    }

}

Обновлено. Чтобы предоставить дополнительную информацию о типах инъекций и контекстах, доступных в Джерси.

Как вы, вероятно, уже догадались, если все, что вам нужно, это доступ к HttpServletRequest, то просто прямое вложение его в ваш Resource или Provider с помощью аннотации @Context вы получите это.

Однако для передачи этих значений в Injectable необходимо использовать AssistedProvider или использовать подход, аналогичный вашему. Но опять-таки вы можете уменьшить это, если встраиваете определение Injectable в Поставщик и вводите HttpServletRequest в класс Provider. В этом случае Injectable сможет получить доступ к экземпляру HttpServletRequest (с момента его появления в области видимости). Я просто обновил свой пример, чтобы показать этот подход.

Инъекция с использованием PerRequestTypeInjectableProvider и SingletonTypeInjectableProvider - это не единственные два параметра, которые вы должны вводить значения в свои ресурсы. Вы также можете вводить значения *Param с помощью StringReaderProvider. Очевидно, что такая инъекция является областью применения.

@Provider
@Named("userProviderParamInjector")
public class UserProviderParam implements StringReaderProvider<Users> {

    @Context
    HttpServletRequest request;

    public StringReader<Users> getStringReader(Class<?> type, Type type1, Annotation[] antns) {
        if(type.equals(Users.class) {
           return null;
        }

        String attributeValue = null;
        for(Annotation a : antns) {
            if((a.getClass().getSimpleName()).equals("AttributeParam")){
               attributeValue = (String)AnnotationUtils.getValue(a);
            }
        }

        return new StringReader<Users>(){
            public Users fromString(String string) {
                // Use the value of the *Param or ignore it and use the attributeValue of our custom annotation.
                return request.getAttribute(attributeValue);
            }

        };

    }

}

Этот Provider будет вызываться для любого *Param, который у вас есть на вашем ресурсе. Таким образом, при Provider, подобном зарегистрированному выше, и ресурсу, подобному приведенному ниже, значение Users будет введено в ваш ресурсный метод.

@Path("/user/")
@Named
public class UserResource {

    @Path("{id}")
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Result<Users> get(@AttributeParam("foo") @PathParam("id") Users user) {
    ...
    }

}

Но, честно говоря, я считаю это злоупотреблением контрактом StringReaderProvider, тогда как прежний метод использования Injectable чувствует себя чище.