[英]Jersey: InjectableProvider not picked up - Spring
I am currently trying to create an InjectableProvider
with Jersey, but I cannot get Jersey to pick it up. 我目前正在尝试使用Jersey创建一个
InjectableProvider
,但是无法让Jersey来接它。
I cannot find any real examples of its usage, or even how to get it picked up besides using the @Provider
annotation on the implementation. 除了在实现上使用
@Provider
注释外,我找不到任何实际用法示例,甚至找不到如何使用它。 The person that seemingly wrote it within Jersey implied in some posts that this is enough to have it picked up. 看似写在泽西岛上的人在某些帖子中暗示这足以使它被拾起。
Do I need to specify some SPI service file, or add it to some factory somewhere? 我需要指定一些SPI服务文件,还是将其添加到某个工厂的某个地方?
Note: I am running within Glassfish 3.1, and using Spring 3.1. 注意:我在Glassfish 3.1中运行,并使用Spring 3.1。 It seems reasonable that Spring may be somehow taking over for the automatic loading of the
Provider
s. Spring可能会以某种方式接管
Provider
的自动加载似乎是合理的。 However, I just don't know. 但是,我只是不知道。 I am not using Spring in anyway to manage the suggested InjectableProvider below, nor am I trying to add it in some other way, which may-well be my problem.
我无论如何都没有使用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());
}
}
Basic Implementation: 基本实现:
import javax.ws.rs.ext.Provider;
@Component // <- Spring Annotation
@Provider // <- Jersey Annotation
public class MyTypeAttributeInjectableProvider
extends AbstractAttributeInjectableProvider<MyType>
{
public MyTypeAttributeInjectableProvider()
{
super(MyType.class);
}
}
Reference 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();
}
Reference link from Jersey developer . 来自Jersey开发人员的参考链接 。
UPDATE : calvinkrishy pointed out two flaws to my thinking. 更新 :calvinkrishy指出了我思考的两个缺陷。
First, I assumed that Jersey was going to kick off scanning for @Provider
s after being kicked off by the traditional Jersey-Spring servlet: com.sun.jersey.spi.spring.container.servlet.SpringServlet
. 首先,我假设Jersey在被传统的Jersey-Spring servlet:
com.sun.jersey.spi.spring.container.servlet.SpringServlet
启动之后,将开始扫描@Provider
。 This was mostly incorrect; 这主要是不正确的。 it does start scanning, but it looks for Spring beans that have the annotation.
它会开始扫描,但会查找具有注释的Spring bean。
Second, I assumed that the PerRequestTypeInjectableProvider
would be asked upon each incoming request for an Injectable
to handle the annotation that it controls. 其次,我假设在每个传入请求中都将要求
PerRequestTypeInjectableProvider
提供一个Injectable
来处理它控制的注释。 This too was wrong. 这也是错的。 The
PerRequestTypeInjectableProvider
is instantiated upon startup, as expected, but Jersey then immediately asks for Injectable
's to handle the given annotation with the given type
, which it determines by scanning the Restful Services that it has--at this point--decided that it manages (which is to say, all of them). PerRequestTypeInjectableProvider
在启动时会按预期方式实例化,但是Jersey随后立即要求Injectable
来处理具有给定type
的给定批注,该批注通过扫描其拥有的Restful Services(此时决定)确定了。管理(也就是说,全部)。
The difference between the PerRequestTypeInjectableProvider
and SingletonTypeInjectableProvider
seems to be that the resulting Injectable
either contains the value without working for it (singleton), or it looks it up each time for the value (per request), thus enabling the value to change per request. PerRequestTypeInjectableProvider
和SingletonTypeInjectableProvider
之间的PerRequestTypeInjectableProvider
似乎是,生成的Injectable
要么包含未使用该值的值(单例),要么每次都针对该值(每个请求)查找该值,从而使该值可以根据请求进行更改。
This threw a smaller wrench into my plans by forcing me to do some extra work in my AttributeInjectable
(code below) rather than passing in some objects, as I had planned, to avoid giving the AttributeInjectable
extra knowledge. 这迫使我在
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();
}
}
I was hoping to be able to pass in the HttpServletRequest
from the Provider
, but the AttributeInjectable
is only instantiated per unique annotation/type. 我希望能够从
Provider
传递HttpServletRequest
,但是AttributeInjectable
仅按唯一的注释/类型实例化。 As I cannot do that, I do that per value lookup, which uses Spring's RequestContextFilter
singleton, which provides a ThreadLocal
mechanism for safely retrieving the HttpServletRequest
(among other things related to the current request). 因为我无法做到这一点,所以我对每个值进行了查找,该查找使用Spring的
RequestContextFilter
单例,该单例提供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>
The result does work, and it makes the code much more readable without forcing various services to extend a base class just to hide the usage of @Context HttpServletRequest request
, which is then used to access the attributes as done above through some helper method. 结果的确起作用,并且在不强制各种服务扩展基类以隐藏
@Context HttpServletRequest request
的使用的情况下,使代码更具可读性,然后使用该方法通过一些辅助方法来访问上述属性,如上文所述。
Then you can do something along the lines of this: 然后,您可以执行以下操作:
@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
}
}
This becomes very convenient as I use a Servlet Filter to ensure that the user has the appropriate privileges to access the service before passing the data, and then I can parse the incoming data (or load it, or whatever) and dump it into the attribute to be loaded. 这非常方便,因为我使用Servlet筛选器来确保用户在传递数据之前具有适当的特权来访问服务,然后可以解析传入的数据(或加载它,或以其他方式)并将其转储到属性中被加载。
If you don't want the above Provider
approach, and you want the base class for accessing the attributes, then here you go: 如果您不希望使用上面的
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 : I thought about my implementation of AbstractAttributeInjectableProvider
, which is itself a generic class that only exists to provide AttributeInjectable
's for a given type, Class<T>
and the supplied AttributeParam
. UPDATE2:我想到我实施
AbstractAttributeInjectableProvider
,它本身就是一个通用类只存在提供AttributeInjectable
对于给定的类型,的Class<T>
和所提供的AttributeParam
。 It's far easier to provide a non- abstract
implementation that is told its type ( Class<T>
) with each requested AttributeParam
, thus avoiding a bunch of constructor-only implementations providing the type for you. 提供一个带有每个请求的
AttributeParam
类型( Class<T>
)的非abstract
实现要容易得多,从而避免了一堆只为您提供类型的构造函数实现。 This also avoids having to write code for every single type that you want to use with the AttributeParam
annotation. 这也避免了必须为要与
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());
}
}
Note: each Injectable
is instantiated once at startup rather than per request, but they are invoked upon each incoming request. 注意:每个
Injectable
在启动时都会实例化一次,而不是在每个请求中实例化一次,但是会在每个传入请求时调用它们。
How are you initializing Jersey? 您如何初始化Jersey?
I will assume you are using Jersey using the jersey-spring servlet. 我假设您正在使用Jersey-spring Servlet使用Jersey。 In which case Jersey would by default initialize using Spring beans and hence your
Provider
has to be a Spring bean. 在这种情况下,Jersey将默认使用Spring Bean进行初始化,因此您的
Provider
必须是Spring Bean。 Try adding a @Named
(or if you do not use atinject @Component
or one of the Spring annotaions) to your Provider
. 尝试将
@Named
添加到Provider
(或者如果不使用atinject @Component
或Spring注释之一)。
An example of using Injectable Providers . 使用Injectable Providers的示例 。
Updated : More clarity on the scope of injection: 更新 :注射范围更清晰:
The Provider
has to be a Singleton, as for all practical purposes its a factory with scope tied to it and there is no need to construct a factory for every request. Provider
必须是一个Singleton,因为出于所有实际目的,它是一个与范围相关联的工厂,并且无需为每个请求都构造一个工厂。 The injection itself would happen per request. 注入本身将根据请求进行。 In other words the
getInjectable
method would be called for every request. 换句话说,
getInjectable
每个请求调用getInjectable
方法。 Did you get a chance to try that? 您有机会尝试一下吗?
OTOH, if you extend the SingletonTypeInjectableProvider
the same object would be injected into your resource every time. OTOH,如果扩展
SingletonTypeInjectableProvider
,则每次都会将同一对象注入资源中。
I am not sure I completely understand your Provider
implementation. 我不确定我是否完全理解您的
Provider
实现。 I believe something like the following should work. 我相信类似以下的方法应该起作用。
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);
}
};
}
}
Updated : To provide more information on the injection types and contexts available in Jersey. 已更新 :提供有关Jersey中可用的注入类型和上下文的更多信息。
As you probably figured by now, if all you need is access to the HttpServletRequest
then just directly injecting it into your Resource
or Provider
using the @Context
annotation will get you that. 正如您现在可能已经想到的那样,如果您只需要访问
HttpServletRequest
则只需使用@Context
批注将其直接注入到Resource
或Provider
中@Context
。
However, to pass those values to the Injectable one has to use a AssistedProvider
or use an approach similar to yours. 但是,要将这些值传递给Injectable必须使用
AssistedProvider
或使用与您类似的方法。 But again you can mitigate that if you inline your Injectable
definition in the Provider and inject the HttpServletRequest
into the Provider
class. 但是,如果您在Provider中内联
Injectable
定义并将HttpServletRequest
注入Provider
类,则可以缓解这种情况。 In that case the Injectable
would be able to access the HttpServletRequest
instance (since its there in scope). 在那种情况下,
Injectable
将能够访问HttpServletRequest
实例(因为它在作用域中)。 I just updated my example to show that approach. 我刚刚更新了示例以显示该方法。
Injection using PerRequestTypeInjectableProvider
and SingletonTypeInjectableProvider
are not the only two options you have to inject values into your resources. 使用
PerRequestTypeInjectableProvider
和SingletonTypeInjectableProvider
进行注入不是将值注入资源的唯一两个选项。 You could also inject using *Param
values using a StringReaderProvider
. 您还可以使用
StringReaderProvider
使用*Param
值进行注入。 Obviously such an injection is request scoped. 显然,这种注入是请求范围内的。
@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);
}
};
}
}
This Provider
would be invoked for any *Param
that you have in your resource. 对于资源中具有的任何
*Param
,都将调用此Provider
。 So with a Provider
like the one above registered and a resource like the one below, the Users
value would be injected into your resource method. 因此,使用上面注册的
Provider
和下面的Provider
这样的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) {
...
}
}
But to be honest with you I consider this an abuse of the StringReaderProvider contract whereas the former technique of using Injectable
feels cleaner. 但老实说,我认为这是对StringReaderProvider合同的滥用,而以前使用
Injectable
技术感觉更干净。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.