我目前正在尝试InjectableProvider使用Jersey 创建一个,但无法让Jersey接它。
InjectableProvider
除了@Provider在实现中使用批注之外,我找不到任何实际用法示例,甚至找不到如何使用它。看似写在泽西岛上的人在某些帖子中暗示这足以使它被拾起。
@Provider
我需要指定一些SPI服务文件,还是将其添加到某个工厂的某个地方?
注意:我正在Glassfish 3.1中运行,并使用Spring 3.1。Spring可能会以某种方式接管Providers 的自动加载似乎是合理的。但是,我只是不知道。我无论如何都没有使用Spring来管理下面建议的InjectableProvider,也没有尝试以其他方式添加它,这很可能是我的问题。
Provider
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:
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(); }
来自Jersey开发人员的参考链接。
更新 :calvinkrishy指出了我思考的两个缺陷。
首先,我假设Jersey @Provider在被传统的Jersey-Spring servlet:开始后将开始扫描s com.sun.jersey.spi.spring.container.servlet.SpringServlet。这主要是不正确的。它会开始扫描,但会查找具有注释的Spring bean。
com.sun.jersey.spi.spring.container.servlet.SpringServlet
其次,我假设PerRequestTypeInjectableProvider将在每个传入请求时要求,Injectable以处理所控制的注释。这也是错的。在PerRequestTypeInjectableProvider启动时被实例化,符合市场预期,但泽西然后立即请求Injectable的处理给定的注释与给定的type,它决定通过扫描RESTful服务,它有- 在这一点上-决定它管理(这也就是说,所有这些)。
PerRequestTypeInjectableProvider
Injectable
type
PerRequestTypeInjectableProvider和之间的区别SingletonTypeInjectableProvider似乎是,结果Injectable要么包含该值而不对其进行处理(单例),要么每次都针对该值(每个请求)查找该值,从而使该值可以根据请求进行更改。
SingletonTypeInjectableProvider
通过强迫我在我的AttributeInjectable代码(下面的代码)中做一些额外的工作,而不是像我计划的那样传入一些对象来避免提供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单例,该单例提供了ThreadLocal一种安全检索HttpServletRequest(与当前请求相关的事物)的机制。
HttpServletRequest
RequestContextFilter
ThreadLocal
<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,使代码更具可读性,而该类仅用于隐藏的用法,然后按上述方法通过一些辅助方法将其用于访问属性。
@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,它本身是一个泛型类,仅存在于AttributeInjectable为给定类型提供的类型,Class<T>以及所提供的AttributeParam。为每个请求提供一个abstract告知其类型(Class<T>)的非实现要容易得多AttributeParam,从而避免了一堆只为构造函数提供类型的实现。这也避免了必须为要与AttributeParam注释一起使用的每种类型编写代码。
AbstractAttributeInjectableProvider
Class<T>
AttributeParam
abstract
@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实例在启动时都会实例化一次,而不是每个请求实例化一次,但是会在每个传入请求时调用它们。
您如何初始化Jersey?
我假设您正在使用Jersey-spring Servlet使用Jersey。在这种情况下,Jersey将默认使用Spring Bean进行初始化,因此您Provider必须是Spring Bean。尝试在中添加一个@Named(或如果不使用atinject @Component或Spring注释之一)Provider。
@Named
@Component
使用Injectable Providers的示例。
更新 :注射范围更清晰:
它Provider必须是一个Singleton,因为出于所有实际目的,它是一个与范围相关联的工厂,因此无需为每个请求都构造一个工厂。注入本身将根据请求进行。换句话说,getInjectable将为每个请求调用该方法。您有机会尝试吗?
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); } }; } }
已更新 :提供有关Jersey可用注射类型和上下文的更多信息。
正如您现在可能已经想到的那样,如果HttpServletRequest仅需要访问,则直接将其注入到您的文档中Resource或Provider使用@Context注释即可得到。
Resource
@Context
但是,要将这些值传递给Injectable必须使用AssistedProvider或使用与您相似的方法。但是,如果您Injectable在Provider中内联定义并将其注入HttpServletRequest到Provider类中,则可以再次缓解这种情况。在那种情况下,Injectable它将能够访问HttpServletRequest实例(因为它在范围内)。我刚刚更新了示例以显示该方法。
AssistedProvider
使用PerRequestTypeInjectableProvider和SingletonTypeInjectableProvider并不是唯一将值注入资源的两个选项。您还可以使用*Param值注入值StringReaderProvider。显然,这种注入是请求范围内的。
*Param
StringReaderProvider
@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值将注入到您的资源方法中。
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感觉更干净。