泽西岛:InjectableProvider未领取-春季 [英] Jersey: InjectableProvider not picked up - Spring

查看:116
本文介绍了泽西岛:InjectableProvider未领取-春季的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在尝试使用Jersey创建一个InjectableProvider,但是我无法让Jersey来拾取它.

I am currently trying to create an InjectableProvider with Jersey, but I cannot get Jersey to pick it up.

除了在实现中使用@Provider批注之外,我找不到任何实际用法示例,甚至找不到如何使用它的方法.看似在泽西岛写信的人在某些帖子中暗示这足以把它拿走.

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. The person that seemingly wrote it within Jersey implied in some posts that this is enough to have it picked up.

我需要指定一些SPI服务文件,还是将其添加到某处的工厂中?

Do I need to specify some SPI service file, or add it to some factory somewhere?

注意:我正在Glassfish 3.1中运行,并使用Spring 3.1. Spring可能会以某种方式接管Provider的自动加载似乎是合理的.但是,我只是不知道.无论如何,我都没有使用Spring来管理下面建议的InjectableProvider,也没有尝试以其他方式添加它,这很可能是我的问题.

Note: I am running within Glassfish 3.1, and using Spring 3.1. It seems reasonable that Spring may be somehow taking over for the automatic loading of the Providers. 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.

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

参考来自泽西岛开发人员的链接.

更新:calvinkrishy指出了我的想法的两个缺陷.

UPDATE: calvinkrishy pointed out two flaws to my thinking.

首先,我假设Jersey被传统的Jersey-Spring servlet:com.sun.jersey.spi.spring.container.servlet.SpringServlet启动之后,将开始扫描@Provider.这主要是不正确的.它会开始扫描,但会查找具有注释的Spring bean.

First, I assumed that Jersey was going to kick off scanning for @Providers after being kicked off by the traditional Jersey-Spring servlet: com.sun.jersey.spi.spring.container.servlet.SpringServlet. This was mostly incorrect; it does start scanning, but it looks for Spring beans that have the annotation.

第二,我假设在每个传入请求中都会要求PerRequestTypeInjectableProvider来处理Injectable以处理其控制的注释.这也是错的.如预期的那样,PerRequestTypeInjectableProvider会在启动时实例化,但是Jersey随即要求Injectable使用给定的type处理给定的批注,这是通过扫描其具有的Restful Services来确定的.点-决定要管理(也就是说,全部).

Second, I assumed that the PerRequestTypeInjectableProvider would be asked upon each incoming request for an Injectable to handle the annotation that it controls. 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).

PerRequestTypeInjectableProviderSingletonTypeInjectableProvider之间的区别似乎是,生成的Injectable要么包含未使用该值的值(单例),要么每次都针对该值(根据请求)对其进行查找,这样就可以根据请求更改值.

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.

这迫使我在我的AttributeInjectable(下面的代码)中做一些额外的工作,而不是像我计划的那样传入一些对象,以避免给AttributeInjectable带来额外的知识,从而使我的计划陷入困境.

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.

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

我希望能够从Provider中传入HttpServletRequest,但是AttributeInjectable仅针对每个唯一的注释/类型进行实例化.我无法做到这一点,所以我按值进行查找,该查找使用Spring的RequestContextFilter单例,该单例提供用于安全地检索HttpServletRequestThreadLocal机制(与当前请求相关的其他事项).

I was hoping to be able to pass in the HttpServletRequest from the Provider, but the AttributeInjectable is only instantiated per unique annotation/type. 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).

<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用法的情况下,使代码更具可读性,然后像上面通过某些辅助方法将其用于访问属性一样.

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.

然后您可以按照以下步骤进行操作:

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
    }
}

这变得非常方便,因为我使用Servlet筛选器来确保用户在传递数据之前具有访问服务的适当特权,然后可以解析传入的数据(或加载它,或以其他方式)并转储它.进入要加载的属性.

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.

如果您不想使用上述Provider方法,并且想要访问属性的基类,那么就可以了:

If you don't want the above Provider approach, and you want the base class for accessing the attributes, then here you go:

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.提供每个请求的AttributeParam都告知其类型(Class<T>)的非abstract实现要容易得多,从而避免了一堆只为您提供类型的仅构造函数实现.这也避免了必须为要与AttributeParam注释一起使用的每种类型编写代码.


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. 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. This also avoids having to write code for every single type that you want to use with the AttributeParam annotation.

@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在启动时实例化一次,而不是在每个请求中实例化一次,但是会在每个传入请求时调用它们.

Note: each Injectable is instantiated once at startup rather than per request, but they are invoked upon each incoming request.

推荐答案

您如何初始化Jersey?

How are you initializing Jersey?

我假设您正在使用Jersey-spring servlet使用Jersey.在这种情况下,默认情况下,Jersey将使用Spring Bean进行初始化,因此,您的Provider必须是Spring Bean.尝试将@Named(或者如果不使用atinject @Component或Spring注释之一)添加到Provider.

I will assume you are using Jersey using the jersey-spring servlet. In which case Jersey would by default initialize using Spring beans and hence your Provider has to be a Spring bean. Try adding a @Named (or if you do not use atinject @Component or one of the Spring annotaions) to your Provider.

使用可注入提供程序的示例.

已更新:更加明确的注入范围:

Updated: More clarity on the scope of injection:

Provider必须是一个Singleton,因为出于所有实际目的,它是一个与范围绑定在一起的工厂,因此无需为每个请求都构造一个工厂.注入本身将根据请求进行.换句话说,将为每个请求调用getInjectable方法.您有机会尝试一下吗?

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. The injection itself would happen per request. In other words the getInjectable method would be called for every request. Did you get a chance to try that?

OTOH,如果您扩展SingletonTypeInjectableProvider,则每次都会向资源中注入相同的对象.

OTOH, if you extend the SingletonTypeInjectableProvider the same object would be injected into your resource every time.

我不确定我是否完全理解您的Provider实现.我相信类似以下的内容应该可以工作.

I am not sure I completely understand your Provider implementation. 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);
            }

        };

    }

}


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


Updated: To provide more information on the injection types and contexts available in Jersey.

您可能现在已经想到,如果只需要访问HttpServletRequest,则只需使用@Context批注将其直接注入ResourceProvider即可.

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.

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

However, to pass those values to the Injectable one has to use a AssistedProvider or use an approach similar to yours. But again you can mitigate that if you inline your Injectable definition in the Provider and inject the HttpServletRequest into the Provider class. In that case the Injectable would be able to access the HttpServletRequest instance (since its there in scope). I just updated my example to show that approach.

使用PerRequestTypeInjectableProviderSingletonTypeInjectableProvider进行注入不是将值注入到资源中的唯一两个选项.您也可以使用StringReaderProvider使用*Param值进行注入.显然,这种注入是请求范围的.

Injection using PerRequestTypeInjectableProvider and SingletonTypeInjectableProvider are not the only two options you have to inject values into your resources. You could also inject using *Param values using a StringReaderProvider. 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);
            }

        };

    }

}

Provider将为您资源中的任何*Param调用.因此,使用上面注册的Provider和下面注册的资源,Users值将被注入到您的资源方法中.

This Provider would be invoked for any *Param that you have in your resource. 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.

@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的技术感觉更干净.

But to be honest with you I consider this an abuse of the StringReaderProvider contract whereas the former technique of using Injectable feels cleaner.

这篇关于泽西岛:InjectableProvider未领取-春季的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆