2012-08-14 130 views
10

我目前正試圖與澤西島創建一個InjectableProvider,但我無法讓澤西撿起來。澤西島:InjectableProvider沒有拿起 - 春季

我找不到任何真實的使用例子,甚至除了在實現中使用@Provider註釋之外,還找不到它的使用方法。看起來在澤西寫的這個人在一些帖子中暗示這足以讓它收拾起來。

我是否需要指定一些SPI服務文件,或將它添加到某個工廠的某處?

注:我在Glassfish 3.1中運行,並使用Spring 3.1。春天可能以某種方式接管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<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}'s {@link HttpServletRequest}. 
    * @return Never {@code null}. Should never be blank. 
    */ 
    String value(); 
} 

參考link from Jersey developer


UPDATE:calvinkrishy指出兩個缺陷,以我的思維。

首先,我認爲澤西在被傳統的Jersey-Spring servlet啓動後將開始掃描@Provider s:com.sun.jersey.spi.spring.container.servlet.SpringServlet。這大多是不正確的;它確實開始掃描,但它會查找帶有註釋的Spring bean。

其次,我認爲PerRequestTypeInjectableProvider將被要求每個傳入請求的Injectable來處理它控制的註釋。這也是錯誤的。如預期的那樣,在啓動時實例化PerRequestTypeInjectableProvider,但澤西然後立即要求Injectable處理給定的type的註釋,它通過掃描Restful Services確定它具有 - 在這一點上 - 決定它管理(也就是說,所有這些)。

PerRequestTypeInjectableProviderSingletonTypeInjectableProvider之間的差異似乎是所得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過濾器,以確保用戶具有相應權限傳遞數據之前訪問該服務,這變得很方便,然後我可以解析傳入的數據(或者加載它,或者其他)並將其轉儲到要加載的屬性中。

如果你不希望上述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,它本身就是一個通用的類,只存在爲給定類型Class<T>和提供的AttributeParam提供AttributeInjectable。提供非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'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()); 
    } 
} 

注:每個Injectable在啓動時,而不是每個請求實例化一次,但他們在每個傳入的請求調用。

回答

6

澤西島初始化過程如何?

我會假設你正在使用澤西島彈簧servlet使用澤西島。在這種情況下,Jersey會默認使用Spring bean進行初始化,因此您的Provider必須是Spring bean。嘗試將@Named(或者如果您不使用atinject @Component或其中一個Spring註釋)添加到您的Provider

An example of using Injectable Providers


更新:在注射的範圍更加清晰:

Provider必須是單身,因爲所有的實際目的與範圍的工廠把它捆起來,也沒有必要爲每個請求構建一個工廠。注射本身會根據要求發生。換句話說,每個請求都會調用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然後就直接用@Context註釋會得到你,它注射到你的ResourceProvider

但是,要將這些值傳遞給Injectable,必須使用AssistedProvider或使用類似於您的方法。但是,如果您在供應商中將Injectable定義內聯並將HttpServletRequest注入到Provider類中,您又可以減輕這一點。在這種情況下,Injectable將能夠訪問HttpServletRequest實例(因爲它在範圍內)。我剛剛更新了我的示例以顯示該方法。

使用PerRequestTypeInjectableProviderSingletonTypeInjectableProvider進行注射並不是您必須將值注入資源的唯一兩個選項。您也可以使用StringReaderProvider使用*Param值進行注入。顯然這樣的注射是請求範圍。

@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前技術感覺更清潔的濫用。

+0

實例化提供程序的bean(或讓它們自動拾取)似乎讓他們拾起,但我出於某種原因被迫使他們成爲單身人士。你有什麼想法,爲什麼它需要我使'PerRequestTypeInjectableProvider'單例作用域?如果我強制它進入請求範圍,它將失敗,併發生異常。這個例子實際上是最初的Injectable Provider例子,它讓我瞭解了這一點。他提供的代碼示例並不是最好的(他將'Provider'和'Injectable'結合在一起)。澤西試圖抽象出「ServletRequest」是愚蠢的。 – pickypg 2012-08-29 01:02:27

+0

更新了更多信息。簡而言之,作爲單例的'Provider'是一個特性,但是應該爲每個請求提供一個新的值。 – calvinkrishy 2012-08-29 03:23:09

+0

啊哈。我錯誤地理解了'PerRequestTypeInjectableProvider'的目的。 Jersey立即構建了「Injectable」,它在掃描它控制的各種Restful Services之後決定它需要。我拿「PerRequest」來表示'Provider'會被每個給定的請求檢查。我希望使用'Provider'將'HttpServletRequest'注入到一個新的'AttributeInjectable'(我自己的類型)中,以'AttributeParam'的名稱來提取'HttpServletRequest#getAttribute(String)'。這樣我的Restful Services可以有乾淨地注入屬性。 – pickypg 2012-08-29 04:26:10