2012-08-14 7 views
10

Ich versuche gerade, eine InjectableProvider mit Jersey zu schaffen, aber ich kann Jersey nicht bekommen, um es aufzuheben.Jersey: InjectibleProvider nicht abgeholt - Frühling

Ich kann keine echten Beispiele für seine Verwendung finden, oder sogar, wie es neben der Verwendung der @Provider Annotation auf der Implementierung abgerufen wird. Die Person, die es scheinbar in Jersey geschrieben hat, hat in einigen Beiträgen angedeutet, dass dies genug ist, um es aufzuheben.

Muss ich eine SPI-Servicedatei angeben oder irgendwo einer Fabrik hinzufügen?

Hinweis: Ich laufe in Glassfish 3.1 und mit Spring 3.1. Es scheint vernünftig, dass Spring irgendwie für das automatische Laden der Provider s übernehmen könnte. Ich weiß es einfach nicht. Ich verwende Spring nicht, um den vorgeschlagenen InjectableProvider zu verwalten, noch versuche ich, es auf andere Weise hinzuzufügen, was mein Problem sein könnte.

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

Grund Umsetzung:

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 from Jersey developer.


UPDATE: calvinkrishy wies darauf hin, zwei Fehler zu meinem Denken.

Zuerst nahm ich an, dass Jersey begann, nach @Provider s zu starten, nachdem es vom traditionellen Jersey-Spring-Servlet gestartet wurde: com.sun.jersey.spi.spring.container.servlet.SpringServlet. Das war meistens falsch; Es beginnt mit dem Scannen, sucht aber nach Spring-Beans mit der Anmerkung.

Zweitens nahm ich an, dass die PerRequestTypeInjectableProvider bei jeder eingehenden Anfrage für eine Injectable aufgefordert werden würde, die Anmerkung, die es steuert, zu behandeln. Auch das war falsch. Die PerRequestTypeInjectableProvider wird wie erwartet beim Start instanziiert, aber Jersey fragt dann sofort nach Injectable, um die gegebene Anmerkung mit der gegebenen type zu behandeln, die es durch das Scannen der Restful Services ermittelt, die es zu diesem Zeitpunkt - zu diesem Zeitpunkt - entschieden hat verwaltet (das heißt, alle von ihnen).

Der Unterschied zwischen dem PerRequestTypeInjectableProvider und SingletonTypeInjectableProvider zu sein scheint, dass die resultierenden Injectable enthält entweder den Wert, ohne dafür zu arbeiten (Singleton) oder es sieht es jedes Mal für den Wert auf (pro Anfrage), damit der Wert zu ermöglichen Änderung pro Anfrage

Dies warf einen kleineren Schraubenschlüssel in meine Pläne, indem er mich zwang, einige zusätzliche Arbeit in meinem AttributeInjectable (Code unten) statt einige Objekte übergeben, wie ich geplant hatte, um die AttributeInjectable zusätzliche Wissen zu geben.

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

Ich hatte gehofft, in der HttpServletRequest vom Provider passieren zu können, aber die AttributeInjectable nur pro einzigartige Anmerkung/Typ instanziiert. Da ich das nicht tun kann, mache ich die Suche nach Wert, die Spring RequestContextFilter Singleton verwendet, die einen ThreadLocal Mechanismus zum sicheren Abrufen der HttpServletRequest (unter anderem im Zusammenhang mit der aktuellen Anfrage) bietet.

<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 viel besser lesbar, ohne verschiedene Dienste zwingt eine Basisklasse zu verlängern nur die Verwendung von @Context HttpServletRequest request zu verstecken, die dann die Attribute verwendet wird, um Zugriff wie oben durch einige getan Hilfsmethode.

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

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

Diese sehr bequem wird, wie ich einen Servlet-Filter verwenden, um sicherzustellen, dass der Benutzer über die entsprechenden Berechtigungen hat den Dienst zuzugreifen, bevor die Daten vorbei, und dann kann ich die eingehenden Daten analysieren (oder laden, oder was auch immer) und sie in das zu ladende Attribut ablegen.

Wenn Sie die oben Provider Ansatz wollen, und Sie wollen die Basisklasse für die Attribute zugreifen, 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 an meine Umsetzung von AbstractAttributeInjectableProvider, die selbst eine generische Klasse ist, die nur existiert, um AttributeInjectable 's für einen gegebenen Typ, Class<T> und die gelieferte AttributeParam bereitzustellen. Es ist viel einfacher, eine nicht-abstract Implementierung bereitzustellen, die ihren Typ (Class<T>) mit jedem angeforderten AttributeParam angibt, wodurch eine Reihe von Nur-Konstruktor-Implementierungen vermieden wird, die den Typ für Sie bereitstellen. Dies vermeidet auch, dass Sie Code für jeden einzelnen Typ schreiben müssen, den Sie mit der Annotation verwenden möchten.

@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 einmal beim Start instanziiert wird und nicht pro Anfrage, aber sie sind auf jeden eingehenden Request aufgerufen.

Antwort

6

Wie initialisierst du Jersey?

Ich nehme an, Sie verwenden Jersey mit dem Jersey-Feder-Servlet. In diesem Fall wird Jersey standardmäßig mit Spring Beans initialisiert und daher muss Ihre Provider eine Spring Bean sein. Versuchen Sie, @Named (oder wenn Sie nicht ein Objekt @Component oder eine der Frühjahr Annotationen verwenden) zu Ihrem Provider.

An example of using Injectable Providers.


Aktualisiert: Mehr Klarheit über den Umfang der Injektion:

Die Provider hat ein Singleton zu sein, wie für alle praktischen Zwecke ist es ein Werk mit Umfang an sie gebunden, und es gibt keine Notwendigkeit, Baue für jede Anfrage eine Fabrik. Die Injektion selbst würde auf Anfrage geschehen. Mit anderen Worten, die Methode getInjectable würde für jede Anfrage aufgerufen. Hast du die Chance, das zu versuchen?

OTOH, wenn Sie die SingletonTypeInjectableProvider erweitern das gleiche Objekt würde jedes Mal in Ihre Ressource injiziert werden.

Ich bin mir nicht sicher, ob ich Ihre Provider Implementierung vollständig verstehe. Ich glaube, etwas wie das Folgende sollte funktionieren.

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

     }; 

    } 

} 

Aktualisiert: Um mehr Informationen über die Injektion Typen und Kontexte in Jersey zur Verfügung stellen.

Wie Sie wahrscheinlich jetzt dachte, wenn alles, was Sie brauchen, ist der Zugang zum HttpServletRequest dann einfach direkt in Ihre Resource oder Provider Injektion des @Context Annotation verwenden werden Sie das bekommen.

Um diese Werte jedoch an das Injecable zu übergeben, müssen Sie einen AssistedProvider verwenden oder einen ähnlichen Ansatz verwenden. Aber wieder können Sie das mildern, wenn Sie Ihre Injectable Definition in den Provider inline und die HttpServletRequest in die Provider Klasse injizieren. In diesem Fall könnte Injectable auf die -Instanz zugreifen (da sie dort enthalten ist). Ich habe gerade mein Beispiel aktualisiert, um diesen Ansatz zu zeigen.

Injektion mit PerRequestTypeInjectableProvider und SingletonTypeInjectableProvider sind nicht die einzigen beiden Optionen, die Sie benötigen, um Werte in Ihre Ressourcen zu injizieren. Sie können auch unter Verwendung von *Param Werte unter Verwendung einer StringReaderProvider injizieren. Offensichtlich ist eine solche Injektion ein Anforderungsgebiet.

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

     }; 

    } 

} 

Diese Provider wäre für jeden *Param, die Sie in Ihrer Ressource aufgerufen werden. Mit einer Provider wie der oben genannten und einer Ressource wie der unten, würde der Users Wert in Ihre Ressourcenmethode injiziert werden.

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

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

} 

Aber um ehrlich zu sein, halte ich dies ein Missbrauch des StringReaderProvider Vertrag, während die frühere Technik der Verwendung Injectable fühlt sich sauberer.

+0

Instantiierung der Provider als Bohnen (oder sie automatisch abgeholt) scheint sie abgeholt zu bekommen, aber ich bin aus irgendeinem Grund gezwungen, sie Singletons zu machen. Haben Sie eine Idee, warum ich den "PerRequestTypeInjectiveProvider" -Singleton auf einen Bereich setzen muss? Wenn ich es in einen Anforderungsbereich dränge, schlägt es mit einer Ausnahme fehl. Das Beispiel war das ursprüngliche Beispiel eines injizierbaren Providers, das mich darauf gebracht hat. Die von ihm bereitgestellten Codebeispiele sind nicht die besten (er kombiniert den "Provider" mit dem "Injectable"). Es ist dumm, dass Jersey versuchte, das 'ServletRequest' wegzufiltern. – pickypg

+0

Aktualisiert mit mehr Informationen. Kurz gesagt, der "Provider", der ein Singleton ist, ist ein Merkmal, aber für jede Anfrage sollte ein neuer Wert bereitgestellt werden. – calvinkrishy

+0

Ah ha. Ich habe den Zweck des 'PerRequestTypeInjectiveProvider' falsch verstanden. Jersey konstruiert sofort die 'Injectible's, die es nach dem Scannen der verschiedenen Restful Services, die es steuert, benötigt. Ich nahm "PerRequest", um zu bedeuten, dass der "Provider" mit jeder gegebenen Anfrage überprüft würde. Ich wollte den 'Provider' verwenden, um' HttpServletRequest' in ein neues 'AttributeInjectable' (meinen eigenen Typ) zu injizieren, um 'HttpServletRequest # getAttribute (String)' mit dem Namen zu extrahieren, der dem 'AttributeParam' gegeben wurde. Auf diese Weise können meine Restful Services Attribute sauber injiziert bekommen. – pickypg

Verwandte Themen