泽西岛: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开始后开始扫描@Provider
: com.sun.jersey.spi.spring.container.servlet.SpringServlet
。 这大多是不正确的; 它确实开始扫描,但它查找具有注释的Spring bean。
其次,我假设每个传入的请求都会询问PerRequestTypeInjectableProvider
,以便Injectable
处理它控制的注释。 这也错了。 正如预期的那样, PerRequestTypeInjectableProvider
在启动时被实例化,但是Jersey随后立即要求Injectable
用给定的type
处理给定的注释,它通过扫描Restful Services确定它具有 – 此时 – 决定它管理(也就是说,所有这些)。
PerRequestTypeInjectableProvider
和SingletonTypeInjectableProvider
之间的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
注释直接将其注入您的Resource
或Provider
@Context
。
但是,要将这些值传递给Injectable,必须使用AssistedProvider
或使用与您类似的方法。 但是,如果您在提供程序中内联您的Injectable
定义并将HttpServletRequest
注入Provider
类,则可以再次缓解这种情况。 在这种情况下, Injectable
将能够访问HttpServletRequest
实例(因为它在范围内)。 我刚刚更新了我的例子来展示这种方法。
使用PerRequestTypeInjectableProvider
和SingletonTypeInjectableProvider
进行注入并不是将值注入资源的唯一两个选项。 您还可以使用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
技术感觉更清洁。