Джерси: InjectableProvider не забрал - весна

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

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

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

Примечание: я работаю в Glassfish 3.1 и использую Spring 3.1. Кажется разумным, что Spring может каким-то образом взять на себя автоматическую загрузку Provider с. Однако я просто не знаю. В любом случае я не использую 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
        extends PerRequestTypeInjectableProvider
{
    protected final Class type;

    public AbstractAttributeInjectableProvider(Class type)
    {
        super(type);

        this.type = type;
    }

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

Базовая реализация:

import javax.ws.rs.ext.Provider;

@Component // <- Spring Annotation
@Provider  // <- Jersey Annotation
public class MyTypeAttributeInjectableProvider
        extends AbstractAttributeInjectableProvider
{
    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}'s {@link HttpServletRequest}.
     * @return Never {@code null}. Should never be blank.
     */
    String value();
}

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


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

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

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

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

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

public class AttributeInjectable implements Injectable
{
    /**
     * The type of data that is being requested.
     */
    private final Class 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 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}.
     *         

* 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, который обеспечивает механизм ThreadLocal для безопасного извлечения HttpServletRequest (среди прочего, связанных с текущим запросом).


    requestContextFilter
    
        org.springframework.web.filter.RequestContextFilter
    


    requestContextFilter
    /path/that/i/wanted/*

Результат работает, и он делает код намного более читабельным, не заставляя различные службы расширять базовый класс, просто чтобы скрыть использование @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 getAttribute(String name, Class 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 и предоставленного AttributeParam. Гораздо проще обеспечить реализацию, отличную от abstract, которая сообщает свой тип (Class) с каждым запрошенным AttributeParam, таким образом избегая кучу реализаций только для конструктора, предоставляющих тип для вас. Это также избавляет от необходимости писать код для каждого отдельного типа, который вы хотите использовать с аннотацией AttributeParam.

@Component
@Provider
public class AttributeParamInjectableProvider
        implements InjectableProvider
{
    /**
     * {@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's 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}.
     * 

* 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 The type of data being accessed by the {@code param}. * @return Never {@code null}. */ protected AttributeInjectable getInjectable(Class type, AttributeParam parameter) { return new AttributeInjectable(type, parameter.value()); } }

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

10
задан pickypg 30 August 2012 в 03:43
поделиться