2012-08-14 12 views
10

Actualmente estoy tratando de crear un InjectableProvider con Jersey, pero no consigo que Jersey lo recoja.Jersey: Inyectable Proveedor no recogido - Primavera

No puedo encontrar ningún ejemplo real de su uso, o incluso cómo obtenerlo recogido además de utilizar la anotación @Provider en la implementación. La persona que aparentemente lo escribió dentro de Jersey implicó en algunos mensajes que esto es suficiente para que se recoja.

¿Debo especificar algún archivo de servicio SPI o agregarlo a alguna fábrica en alguna parte?

Nota: Estoy ejecutando Glassfish 3.1 y utilizando Spring 3.1. Parece razonable que Spring se haga cargo de la carga automática del Provider s. Sin embargo, simplemente no lo sé. No utilizo Spring de todos modos para administrar el InjectableProvider sugerido a continuación, ni estoy tratando de agregarlo de alguna otra manera, que bien podría ser mi problema.

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

Implementación básica:

import javax.ws.rs.ext.Provider; 

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

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

Referencia link from Jersey developer.


ACTUALIZACIÓN: calvinkrishy señaló dos defectos a mi forma de pensar.

Primero, asumí que Jersey iba a comenzar a escanear para @Provider s después de ser iniciado por el servlet Jersey-Spring tradicional: com.sun.jersey.spi.spring.container.servlet.SpringServlet. Esto fue en su mayoría incorrecto; comienza a escanear, pero busca frijoles primavera que tengan la anotación.

En segundo lugar, supone que el PerRequestTypeInjectableProvider se pediría a cada solicitud entrante para una Injectable para manejar la anotación que controla. Esto también estuvo mal. El PerRequestTypeInjectableProvider se instancia al momento del inicio, como se esperaba, pero Jersey inmediatamente solicita Injectable para manejar la anotación dada con el type dado, que determina mediante el escaneo de los Servicios Restful que tiene - en este punto - decidió que administra (es decir, todos).

La diferencia entre el PerRequestTypeInjectableProvider y SingletonTypeInjectableProvider parece ser que el resultado Injectable contiene o bien el valor sin trabajar por ello (Singleton), o que se ve hacia arriba cada vez que por el valor (por solicitud), lo que permite que el valor de cambio por solicitud.

Esto arrojó una llave más pequeña en mis planes al obligarme a hacer un trabajo extra en mi AttributeInjectable (código debajo) en lugar de pasar algunos objetos, como había planeado, para evitar dar el conocimiento adicional 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(); 
    } 
} 

Tenía la esperanza de ser capaz de pasar en el HttpServletRequest del Provider, pero el AttributeInjectable solamente se crea una instancia única por cada anotación/tipo. Como no puedo hacer eso, lo hago por búsqueda de valor, que usa el singleton de Spring RequestContextFilter, que proporciona un mecanismo ThreadLocal para recuperar de manera segura HttpServletRequest (entre otras cosas relacionadas con la solicitud actual).

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

El resultado hace el trabajo, y hace que el código sea más fácil de leer sin forzar varios servicios para extender una clase base sólo para ocultar el uso de @Context HttpServletRequest request, que luego se utiliza para acceder a los atributos como se ha hecho anteriormente a través de algunos método de ayuda.

entonces usted puede hacer algo en la línea de lo siguiente:

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

Esto es muy conveniente, ya que utilizo un filtro de servlet para asegurar que el usuario tiene los privilegios apropiados para acceder al servicio antes de pasar los datos, y luego puedo analizar los datos entrantes (o cargarlos, o lo que sea) y volcarlos en el atributo que se va a cargar.

Si no desea que el Provider enfoque anterior, y desea que la clase base para acceder a los atributos, entonces aquí van:

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: pensé en mi aplicación de AbstractAttributeInjectableProvider, que en sí es una clase genérica que sólo existe para proporcionar AttributeInjectable 's para un tipo dado, Class<T> y el suministrado AttributeParam. Es mucho más fácil para proporcionar una aplicación no abstract que se cuenta su tipo (Class<T>) con cada pedido AttributeParam, evitando así un montón de implementaciones constructor de sólo proporcionar el tipo para usted. Esto también evita tener que escribir código para cada tipo que desee usar con la anotación 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'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()); 
    } 
} 

Nota: cada Injectable se instancia una vez en el arranque en lugar de por la petición, sino que se invocan a cada solicitud entrante.

Respuesta

6

¿Cómo estás inicializando Jersey?

asumiré que está utilizando Jersey utilizando el servlet-jersey de primavera. En este caso, Jersey se inicializaría por defecto con Spring beans y, por lo tanto, su Provider tiene que ser un Spring Bean. Trate de añadir un @Named (o si no se utiliza atinject @Component o uno de los annotaions primavera) a su Provider.

An example of using Injectable Providers.


Actualizado: Más claridad sobre el alcance de la inyección:

El Provider tiene que haber un Singleton, como para todos los propósitos prácticos es una fábrica con alcance atado a ella y no hay necesidad de construir una fábrica para cada pedido. La inyección en sí misma ocurriría por solicitud. En otras palabras, se llamaría al método getInjectable para cada solicitud. ¿Tuviste la oportunidad de probar eso?

OTOH, si extiende el SingletonTypeInjectableProvider el mismo objeto se inyectará en su recurso todo el tiempo.

No estoy seguro de que entiendo completamente su implementación Provider. Creo que algo como lo siguiente debería funcionar.

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

     }; 

    } 

} 

Actualizado: Para proporcionar más información sobre los tipos de inyección y contextos disponibles en Jersey.

Como probablemente descubierto por ahora, si todo lo que necesita es acceso a la HttpServletRequest a continuación, sólo directamente inyectarlo en su Resource o Provider utilizando la anotación @Context le consiga eso.

Sin embargo, para pasar esos valores al Inyectable uno tiene que usar un AssistedProvider o utilizar un enfoque similar al suyo. Pero una vez más puede mitigar eso si alinea su definición Injectable en el Proveedor e inyecta el HttpServletRequest en la clase Provider. En ese caso, el Injectable podría acceder a la instancia HttpServletRequest (dado que está dentro del alcance). Acabo de actualizar mi ejemplo para mostrar ese enfoque.

La inyección usando PerRequestTypeInjectableProvider y SingletonTypeInjectableProvider no son las únicas dos opciones que tiene para inyectar valores en sus recursos. También puede inyectar utilizando los valores *Param usando StringReaderProvider. Obviamente, tal inyección se solicita con alcance.

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

     }; 

    } 

} 

Este Provider sería invocado por cualquier *Param que usted tiene en su recurso. Entonces, con un Provider como el anterior registrado y un recurso como el siguiente, el valor Users se inyectará en su método de recursos.

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

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

} 

Pero para ser honesto con usted, considero esto un abuso del contrato StringReaderProvider mientras que la primera técnica de usar Injectable siente más limpio.

+0

Instanciar los proveedores como frijoles (o hacer que se recojan automáticamente) parece que los recogen, pero estoy por algún motivo forzado a convertirlos en simples. ¿Tiene alguna idea de por qué me obliga a hacer el singleton 'PerRequestTypeInjectableProvider'? Si lo forzo a un alcance de solicitud, falla con una excepción. El ejemplo fue en realidad el ejemplo original de Proveedor de inyectables que me ayudó en esto. Los ejemplos de código que proporciona no son los mejores (combina el 'Provider' con' Injectable'). Es estúpido que Jersey haya intentado abstraer el 'ServletRequest'. – pickypg

+0

Actualizado con más información. En resumen, el 'Proveedor 'es un singleton es una función, pero se debe proporcionar un nuevo valor para cada solicitud. – calvinkrishy

+0

Ah, ja. Estaba malinterpretando el propósito del 'PerRequestTypeInjectableProvider'. Jersey inmediatamente construye el 'Inyectable's que decide que necesita después de escanear los diversos Servicios Restful que controla. Tomé "PerRequest" para significar que el 'Provider' se verificaría con cada solicitud dada. Quería usar 'Provider' para inyectar' HttpServletRequest' en un nuevo 'AttributeInjectable' (mi propio tipo) para extraer' HttpServletRequest # getAttribute (String) 'con el nombre dado al' AttributeParam'. De esta forma mis Servicios de descanso podrían tener atributos inyectados limpiamente. – pickypg

Cuestiones relacionadas