泽西岛:InjectableProvider没有拿起 – spring

我目前正在尝试使用Jersey创建一个InjectableProvider ,但是我无法让Jersey拿起它。

除了在实现上使用@Provider注释之外,我找不到任何使用它的真实示例,甚至找不到它如何获取它。 看似在泽西岛内写作的人在某些post中暗示这足以让它捡起来。

我是否需要指定一些SPI服务文件,或者将其添加到某个工厂?

注意:我在Glassfish 3.1中运行,并使用Spring 3.1。 Spring可能以某种方式接管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 extends PerRequestTypeInjectableProvider { protected final Class type; public AbstractAttributeInjectableProvider(Class type) { super(type); this.type = type; } @Override public Injectable getInjectable(ComponentContext componentContext, AttributeParam attributeParam) { return new AttributeInjectable(type, attributeParam.value()); } } 

基本实施:

 import javax.ws.rs.ext.Provider; @Component // <- Spring Annotation @Provider // <- Jersey Annotation public class MyTypeAttributeInjectableProvider extends AbstractAttributeInjectableProvider { 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(); } 

泽西开发者的参考链接 。


更新 :calvinkrishy指出了我思考的两个缺陷。

首先,我认为泽西岛将在被传统的Jersey-Spring servlet开始后开始扫描@Providercom.sun.jersey.spi.spring.container.servlet.SpringServlet 。 这大多是不正确的; 它确实开始扫描,但它查找具有注释的Spring bean。

其次,我假设每个传入的请求都会询问PerRequestTypeInjectableProvider ,以便Injectable处理它控制的注释。 这也错了。 正如预期的那样, PerRequestTypeInjectableProvider在启动时被实例化,但是Jersey随后立即要求Injectable用给定的type处理给定的注释,它通过扫描Restful Services确定它具有 – 此时 – 决定它管理(也就是说,所有这些)。

PerRequestTypeInjectableProviderSingletonTypeInjectableProvider之间的PerRequestTypeInjectableProvider似乎是生成的Injectable包含不为它工作的值(单例),或者它每次查找值(每个请求),从而使每个请求更改值。

通过强迫我在我的AttributeInjectable (下面的代码)中做一些额外的工作,而不是按照我的计划传递一些对象,以避免给出AttributeInjectable额外的知识,这给我的计划投入了一个小扳手。

 public class AttributeInjectable implements Injectable { /** * The type of data that is being requested. */ private final Class 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 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}. * 

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

我希望能够从Provider传递HttpServletRequest ,但是AttributeInjectable仅根据唯一注释/类型进行实例化。 由于我不能这样做,我按照值查找执行该操作,它使用Spring的RequestContextFilter单例,它提供了一个ThreadLocal机制来安全地检索HttpServletRequest (以及与当前请求相关的其他内容)。

  requestContextFilter  org.springframework.web.filter.RequestContextFilter    requestContextFilter /path/that/i/wanted/*  

结果确实有效,并且它使代码更具可读性而不强制各种服务扩展基类只是为了隐藏@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 } } 

这变得非常方便,因为我使用Servletfilter来确保用户在传递数据之前具有访问服务的适当权限,然后我可以解析传入的数据(或加载它,或者其他)并将其转储到属性中要加载。

如果您不想使用上面的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 getAttribute(String name, Class 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实现,它本身就是一个generics类,只存在为给定类型Class和提供的AttributeParam提供AttributeParam 。 使用每个请求的AttributeParam提供一个非abstract实现(告诉它的类型( Class ))要容易得多,从而避免了为您提供类型的一堆构造函数实现。 这也避免了必须为要与AttributeParam注释一起使用的每种类型编写代码。

 @Component @Provider public class AttributeParamInjectableProvider implements InjectableProvider { /** * {@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}. * 

* 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 The type of data being accessed by the {@code param}. * @return Never {@code null}. */ protected AttributeInjectable getInjectable(Class type, AttributeParam parameter) { return new AttributeInjectable(type, parameter.value()); } }

注意:每个Injectable在启动时而不是按请求实例化一次,但是在每个传入请求时都会调用它们。

你是如何初始化泽西岛的?

我假设您使用泽西弹簧servlet使用Jersey。 在这种情况下,Jersey默认使用Spring bean初始化,因此您的Provider必须是Spring bean。 尝试将@Named (或者如果不使用atinject @Component或其中一个Spring注释)添加到Provider

使用可注射提供程序的示例 。


更新 :更清晰的注射范围:

Provider必须是单身人士,就所有实际目的而言,其工厂的范围与其相关,并且不需要为每个请求构建工厂。 注射本身将按照要求进行。 换句话说, getInjectable每个请求调用getInjectable方法。 你有机会试试吗?

OTOH,如果扩展SingletonTypeInjectableProvider ,每次都会将相同的对象注入到资源中。

我不确定我是否完全理解您的Provider实现。 我相信以下内容应该有效。

 public class UserProvider extends PerRequestTypeInjectableProvider{ public UserProvider(){ super(Users.class); } @Context HttpServletRequest request; @Override public Injectable getInjectable(ComponentContext cc, AttributeParam a) { String attributeValue = AnnotationUtils.getValue(a); return new Injectable(){ public Users getValue() { System.out.println("Called"); //This should be called for each request return request.getAttribute(attributeValue); } }; } } 

更新 :提供有关Jersey中可用的注射类型和上下文的更多信息。

正如您现在可能想到的那样,如果您只需要访问HttpServletRequest那么只需使用@Context注释直接将其注入您的ResourceProvider @Context

但是,要将这些值传递给Injectable,必须使用AssistedProvider或使用与您类似的方法。 但是,如果您在提供程序中内联您的Injectable定义并将HttpServletRequest注入Provider类,则可以再次缓解这种情况。 在这种情况下, Injectable将能够访问HttpServletRequest实例(因为它在范围内)。 我刚刚更新了我的例子来展示这种方法。

使用PerRequestTypeInjectableProviderSingletonTypeInjectableProvider进行注入并不是将值注入资源的唯一两个选项。 您还可以使用StringReaderProvider使用*Param值进行注入。 显然,这样的注入是请求范围。

 @Provider @Named("userProviderParamInjector") public class UserProviderParam implements StringReaderProvider { @Context HttpServletRequest request; public StringReader 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(){ 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); } }; } } 

将为您资源中的任何*Param调用此Provider 。 因此,对于像上面注册的那样的Provider和下面的资源, Users值将被注入到您的资源方法中。

 @Path("/user/") @Named public class UserResource { @Path("{id}") @GET @Produces(MediaType.APPLICATION_JSON) public Result get(@AttributeParam("foo") @PathParam("id") Users user) { ... } } 

但说实话,我认为这是对StringReaderProvider合同的滥用,而以前使用Injectable技术感觉更清洁。

Interesting Posts