泽西岛:InjectableProvider没有拿起 - 春天

pic*_*ypg 10 java spring jersey servlet-filters injectableprovider

我目前正在尝试InjectableProvider用泽西岛创造一个,但我不能让泽西拿起它.

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

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

注意:我在Glassfish 3.1中运行,并使用Spring 3.1.Spring可能以某种方式接管自动加载Providers 似乎是合理的.但是,我只是不知道.我不是在使用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());
    }
}
Run Code Online (Sandbox Code Playgroud)

基本实施:

import javax.ws.rs.ext.Provider;

@Component // <- Spring Annotation
@Provider  // <- Jersey Annotation
public class MyTypeAttributeInjectableProvider
        extends AbstractAttributeInjectableProvider<MyType>
{
    public MyTypeAttributeInjectableProvider()
    {
        super(MyType.class);
    }
}
Run Code Online (Sandbox Code Playgroud)

参考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();
}
Run Code Online (Sandbox Code Playgroud)

泽西开发者的参考链接.


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

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

其次,我假设PerRequestTypeInjectableProvider每个传入的请求都要求Injectable处理它控制的注释.这也错了.在PerRequestTypeInjectableProvider启动时被实例化,符合市场预期,但泽西然后立即请求Injectable的处理给定的注释与给定的type,它决定通过扫描它具有RESTful服务-在这一点上-决定它管理(这就是说,所有这些).

PerRequestTypeInjectableProvider和之间的区别SingletonTypeInjectableProvider似乎是结果Injectable要么包含没有工作的值(单例),要么每次查找值(每个请求),从而使每个请求更改值.

通过强迫我在我的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();
    }
}
Run Code Online (Sandbox Code Playgroud)

我希望能够在传递HttpServletRequestProvider,但AttributeInjectable只有每独特的注解/类型实例.由于我不能这样做,我按照值查找执行此操作,该查找使用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>
Run Code Online (Sandbox Code Playgroud)

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

这变得非常方便,因为我使用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
    }
}
Run Code Online (Sandbox Code Playgroud)

UPDATE2:我考虑过我的实现AbstractAttributeInjectableProvider,它本身就是一个泛型类,只存在AttributeInjectable为给定类型Class<T>提供的,以及提供的AttributeParam.提供一个非abstract实现被告知Class<T>每个请求的type()要容易得多AttributeParam,从而避免了为你提供类型的一堆构造函数实现.这也避免了必须为要与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());
    }
}
Run Code Online (Sandbox Code Playgroud)

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

cal*_*shy 6

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

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

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


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

Provider必须是一个Singleton,因为所有的实际目的与范围的工厂把它捆起来,也没有必要建立一个工厂为每个请求.注射本身将按照要求进行.换句话说,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);
            }

        };

    }

}
Run Code Online (Sandbox Code Playgroud)

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

正如你现在想象的那样,如果您只需要访问,HttpServletRequest那么只需将其直接注入您ResourceProvider使用@Context注释即可获得.

但是,要将这些值传递给Injectable,必须使用AssistedProvider或使用与您类似的方法.但同样可以缓解,如果你的内嵌Injectable定义的供应商和注入HttpServletRequestProvider类.在这种情况下,Injectable将能够访问HttpServletRequest实例(因为它在范围内).我刚刚更新了我的示例以显示该方法.

使用注入PerRequestTypeInjectableProvider并且SingletonTypeInjectableProvider不是将值注入资源的唯一两个选项.您也可以*Param使用a 注入值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);
            }

        };

    }

}
Run Code Online (Sandbox Code Playgroud)

Provider将针对*Param您资源中的任何内容调用.因此,与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) {
    ...
    }

}
Run Code Online (Sandbox Code Playgroud)

但老实说,我认为这是对StringReaderProvider合同的滥用,而以前使用的技术Injectable感觉更干净.