Jersey: InjectableProvider nicht abgeholt - Frühling

Ich bin versucht derzeit, erstellen Sie eine InjectableProvider mit Jersey, aber ich Schaffe es nicht, Jersey, um ihn abzuholen.

Ich finde keine wirklichen Beispiele für seine Verwendung, oder sogar, wie man es nahm neben der Verwendung der @Provider - Anmerkung für die Umsetzung. Die person, die scheinbar schrieb er in Jersey implizit in manchen Beiträgen, dass dies genug ist, um es abgeholt.

Muss ich angeben, einige SPI-service-Datei, oder fügen Sie es, um eine Fabrik irgendwo?

Hinweis: ich verwende in Glassfish 3.1, und mit Spring 3.1. Es scheint vernünftig, dass der Frühling vielleicht irgendwie die übernahme der für das automatische laden der Providers. Aber ich weiß einfach nicht. Ich bin nicht mit Feder eh zu verwalten, die vorgeschlagenen InjectableProvider unten, noch will ich hinzufügen, dass es in irgendeiner anderen Art und Weise, das kann-auch mein problem.

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());
    }
}

Grundlegende Implementierung:

import javax.ws.rs.ext.Provider;

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

Referenz 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();
}

Referenz link aus Jersey Entwickler.


UPDATE: calvinkrishy wies darauf hin zwei Fehler, mein denken.

Zuerst bin ich davon ausgegangen, dass Jersey Los war, kick-off-scanning für @Providers nach dem Startschuss durch den traditionellen Jersey-Spring-servlet: com.sun.jersey.spi.spring.container.servlet.SpringServlet. Dies war vor allem falsch; es tut, starten Sie das scanning, aber es sieht für die Spring-Bean, die die annotation.

Zweiten bin ich davon ausgegangen, dass die PerRequestTypeInjectableProvider gefragt würde, auf jede eingehende Anfrage für ein Injectable Griff die Anmerkung, dass es steuert. Auch das war falsch. Die PerRequestTypeInjectableProvider instanziiert wird beim Start, wie erwartet, aber Jersey, dann fragt sofort für Injectable's zum behandeln der gegebenen annotation mit dem gegebenen type, die er bestimmt, durch das Scannen der Restful-Dienste, die es hat-an dieser Stelle-entschieden, dass es ihm gelingt (und das ist zu sagen, alle von Ihnen).

Den Unterschied zwischen den PerRequestTypeInjectableProvider und SingletonTypeInjectableProvider scheint zu sein, dass die daraus resultierenden Injectable enthält entweder den Wert ohne die für Sie tätig sind (singleton), oder sucht er es jedes mal um den Wert (pro Antrag), so dass der Wert änderung pro Anfrage.

Dieser warf ein kleiner Schraubenschlüssel in meine Pläne durch und zwingt mich zu tun, einige zusätzliche Arbeit in meiner AttributeInjectable (code unten) anstatt in einigen Objekten, als ich geplant hatte, zu verhindern, dass die AttributeInjectable zusätzliches wissen.

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();
    }
}

Hoffte ich, in der Lage sein pass in die HttpServletRequest von der Provider, aber die AttributeInjectable wird nur instanziiert für jeden eindeutigen annotation/Typ. Als ich das nicht tun kann, ich tun, dass der pro-Wert-lookup, verwendet Spring RequestContextFilter singleton, die eine ThreadLocal Mechanismus zum sicheren abrufen der HttpServletRequest (unter anderem im Zusammenhang mit der aktuellen Anfrage).

<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>

Das Ergebnis funktioniert, und es macht den code sehr viel besser lesbar, ohne dass die verschiedenen Dienstleistungen zu erweitern, eine Basisklasse, die nur zu verbergen, die Verwendung von @Context HttpServletRequest request, die dann verwendet werden, um Zugriff auf die Attribute wie oben durch einige Helfer-Methode.

Dann können Sie tun, etwas entlang der Linien von:

@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
    }
}

Dieser wird sehr bequem, wie ich Sie verwenden einen Servlet-Filter, um sicherzustellen, dass der Benutzer die entsprechenden Berechtigungen zum Zugriff auf den service vor der übergabe der Daten, und dann kann ich analysieren die eingehenden Daten (oder laden Sie es, oder was auch immer) und werfen Sie es in das Attribut geladen werden.

Wenn Sie nicht möchten, dass die oben Provider Ansatz, und Sie möchten, dass die Basisklasse für den Zugriff auf die Attribute, dann hier gehen Sie:

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: ich dachte über meine Umsetzung der AbstractAttributeInjectableProvider, der selbst eine generische Klasse, die nur existiert, um zu bieten AttributeInjectable's für einen bestimmten Typ, der Class<T> und der mitgelieferten AttributeParam. Es ist viel einfacher, um eine nicht-abstract Implementierung, die gesagt wird, seinen Typ (Class<T>) mit den jeweils angeforderten AttributeParam, so dass eine Reihe von Konstruktor-nur Implementierungen, die den Typ für Sie. Dies auch vermeiden, schreiben Sie den code für jede einzelne Art, die Sie verwenden möchten, mit der AttributeParam annotation.

@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'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}.
     * <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());
    }
}

Hinweis: jeder Injectable instanziiert wird einmal beim Start eher als pro Anfrage, aber Sie sind aufgerufen, auf jede eingehende Anfrage.

InformationsquelleAutor pickypg | 2012-08-14
Schreibe einen Kommentar