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

时间:2012-08-14 01:07:23

标签: java spring jersey servlet-filters injectableprovider

我目前正在尝试用泽西岛创建一个InjectableProvider,但我无法让泽西拿起它。

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

我是否需要指定一些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<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();
}

参考link from Jersey developer


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

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

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

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

这迫使我在我的AttributeInjectable(下面的代码)中做了一些额外的工作,而不是按照我的计划传递一些对象,以避免给出{{1}额外的知识。

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(); } } 传递HttpServletRequest,但Provider仅根据唯一注释/类型进行实例化。由于我不能这样做,我按照值查找执行此操作,该查找使用Spring的AttributeInjectable单例,它提供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> 的用法,然后通过一些用来访问属性,如上所述辅助方法。

然后你可以按照以下方式做点什么:

@Context HttpServletRequest request

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

如果您不想使用上述@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 } } 方法,并且您希望基类访问属性,那么请转到:

Provider

UPDATE2 :我考虑过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 } } 的实现,AbstractAttributeInjectableProvider本身就是一个通用类,只为给定类型提供AttributeInjectable,{{1 }和提供的Class<T>。使用每个请求的AttributeParam提供一个非abstract实现被告知其类型(Class<T>)要容易得多,从而避免了为您提供类型的一堆构造函数实现。这也避免了必须为要与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()); } } 在启动时实例化一次,而不是按请求实例化,但每次传入请求时都会调用它们。

1 个答案:

答案 0 :(得分:6)

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

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

An example of using Injectable Providers


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

Provider必须是一个单身人士,就所有实际目的而言,它是一个与其相关联的工厂,并且不需要为每个请求构建工厂。注射本身将按照要求进行。换句话说,将为每个请求调用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);
            }

        };

    }

}

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

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

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

使用PerRequestTypeInjectableProviderSingletonTypeInjectableProvider进行注入并不是将值注入资源的唯一两个选项。您还可以使用*Param使用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);
            }

        };

    }

}

将为您资源中的任何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) { ... } } 值将被注入资源方法。

Injectable

但老实说,我认为这是对StringReaderProvider合同的滥用,而以前使用{{1}}的技术感觉更清晰。