【问题标题】:Java/Jersey - creating own injection resolver with ParamInjectionResolver - strange behaviorJava/Jersey - 使用 ParamInjectionResolver 创建自己的注入解析器 - 奇怪的行为
【发布时间】:2016-09-22 09:23:37
【问题描述】:

我正在尝试创建一个注入解析器。我有一个数据类:

public class MyData {
    ...
}

我有以下注释:

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyDataInject {
}

我的注入解析器如下所示:

public class MyDataInjectionResolver extends ParamInjectionResolver<MyDataInject> {
    public MyDataInjectionResolver () {
        super(MyDataValueFactoryProvider.class);
    }

    @Singleton
    public static class MyDataValueFactoryProvider extends AbstractValueFactoryProvider {
        @Inject
        public MyDataValueFactoryProvider(MultivaluedParameterExtractorProvider provider, ServiceLocator locator) {
            super(provider, locator, Parameter.Source.UNKNOWN);
        }

        @Override
        protected Factory<?> createValueFactory(Parameter parameter) {
            System.out.println(parameter.getRawType());
            System.out.println(Arrays.toString(parameter.getAnnotations()));
            System.out.println("------------------------------------------------------------------");
            System.out.println();

            ... create factory and return ...
        }
    }
}

我的绑定如下:

bind(MyDataValueFactoryProvider.class).to(ValueFactoryProvider.class).in(Singleton.class);
bind(MyDataInjectionResolver.class).to(new TypeLiteral<InjectionResolver<MyDataInject>>() {}).in(Singleton.class);

为了简洁起见,我将实际工厂的实现放在一边。一切正常,但我注意到一些我无法解释的行为。我正在使用以下 JAX-RS 资源进行测试:

@Path("test")
public class Test {
    @GET
    public Response test(@MyDataInject @Valid MyData data) {
        return Response.status(Response.Status.OK).entity("Hello world!").build();
    }
}
  • 我注意到的第一件事是MyDataValueFactoryProvider.createValueFactory 在启动期间被调用了两次。这是为什么?这闻起来像一些错误。好处是工厂只在客户端发出请求时访问一次。
  • 另一个观察结果是,如果我删除资源中的 @MyDataInject 注释,如下所示 (*),MyDataValueFactoryProvider.createValueFactory 仍会被调用。这是为什么?这很奇怪,因为它应该只绑定到 @MyDataInject? (update) 当参数不属于MyData 类时,它甚至会被调用,参见下面的第二个变体。

(*) 没有@MyDataInject注解的资源:

@Path("test")
public class Test {
    @GET
    public Response test(/*@MyDataInject*/ @Valid MyData data) {
        return Response.status(Response.Status.OK).entity("Hello world!").build();
    }
}

另一种变体:

@Path("test")
public class Test {
    @GET
    public Response test(@Valid SomeOtherClass data) {
        return Response.status(Response.Status.OK).entity("Hello world!").build();
    }
}

【问题讨论】:

    标签: java spring dependency-injection jersey hk2


    【解决方案1】:

    在启动时,Jersey 会构建所有资源的内部模型。 Jersey 使用此模型来处理请求。该模型的一部分由所有资源方法及其所有参数组成。更进一步,Jersey 还将验证模型以确保它是有效的模型。模型中的某些无效内容可能会导致 Jersey 在运行时无法处理该模型。所以这个验证是为了保护我们。

    话虽如此,验证过程的一部分是验证方法参数。有一些规则可以控制我们可以拥有的参数。例如,@QueryParam 参数必须满足the javadoc 中提到的要求之一:

    1. 成为原始类型
    2. 有一个接受单个字符串参数的构造函数
    3. 有一个名为 valueOffromString 的静态方法,它接受单个字符串参数(例如,请参阅 Integer.valueOf(String)
    4. 有一个ParamConverterProvider JAX-RS 扩展 SPI 的注册实现,它返回一个 ParamConverter 实例,该实例能够对该类型进行“来自字符串”的转换。
    5. List&lt;T&gt;Set&lt;T&gt;SortedSet&lt;T&gt;,其中T 满足上述2、3 或4。生成的集合是只读的。

    这里有一些你可以尝试的东西。使用以下任意类添加@QueryParam

    public class Dummy {
      public String value;
    }
    
    @GET
    public Response get(@QueryParam("dummy") Dummy dummy) {}
    

    请注意,Dummy 类不满足上面列出的任何要求。当您运行应用程序时,您应该会在启动时收到异常,从而导致应用程序失败。例外情况类似于

    ModelValidationException: No injection source for parameter ...
    

    这意味着模型验证失败,因为 Jersey 不知道如何从查询参数创建 Dummy 实例,因为它不遵循允许的规则。

    好的。那么这一切与您的问题有什么关系?好吧,所有参数注入都需要ValueFactoryProvider 才能为其提供值。如果没有,则无法在运行时创建参数。因此 Jersey 通过检查是否存在返回 FactoryValueFactoryProvider 来验证参数。 Jersey 在运行时调用以获取Factory 的方法就是您提到的方法:createValueFactory

    现在请记住,当我们实现 createValueFactory 时,我们可以返回 Factory 或返回 null。我们应该如何实现它,是检查Parameter 参数以查看我们是否可以处理该参数。比如

    protected Factory<?> createValueFactory(Parameter parameter) {
       if (parameter.getRawType() == Dummy.class
           && parameter.isAnnotationPresent(MyAnnoation.class)) {
         return new MyFactory();
       }
       return null;
    }
    

    所以我们在这里告诉 Jersey 这个ValueFactoryProvider 可以处理什么。在这种情况下,我们可以处理 Dummy 类型的参数,并且如果参数带有 @MyAnnotation 注释。

    那么在启动验证过程中会发生什么,对于每个参数,Jersey 都会遍历每个注册的ValueFactoryProvider,看看是否有一个可以处理该参数。它可以知道的唯一方法是它是否调用了createValueFactory 方法。如果有一个返回Factory,那么它是成功的。如果遍历了所有的ValueFactoryProviders并且都返回null,那么模型是无效的,我们会得到模型验证异常。需要注意的是,内部有一堆ValueFactoryProviders 用于参数注解,如@QueryParam@PathParam 等。

    【讨论】:

    • 感谢您的深入解释。我现在明白了。但是为什么 Jersey 会问我的 MyDataValueFactoryProvider 是否可以为 SomeOtherclassMyData without @MyDataInject 提供非空工厂,而该提供程序仅通过 bind(MyDataInjectionResolver.class).to(new TypeLiteral&lt;InjectionResolver&lt;MyDataInject&gt;&gt;() {}).in(Singleton.class) 绑定到 MyDataInject?对于SomeOtherClass或者MyData without @MyDataInject,他应该提前知道那个provider不适用吧?
    • 注入解析器不用于 Jersey 资源方法的参数注入。您正在使用的类,即ParamInjectionResolverAbstractValueFactoryProvider,这些只是用于参数注入的辅助 DRY 类 Jersey。 @PathParam@QueryParam 之类的参数也可以注入到字段和构造函数中。那是使用注入解析器的时候。因此,这两个帮助类的组合结合起来允许在任何地方进行注入。但是参数注入只需要ValueFactoryProvider
    • AbstractValueFactoryProvider 也只是一个抽象的辅助类,它部分实现了ValueFactoryProvider。您可以尝试实现自己的ValueFactoryProvider。就在界面上。您应该会看到,这仅适用于参数注入。
    • 啊,现在明白了。我以为注射剂无处不在。我不明白 HK-2 只为构造函数、字段、......谢谢
    • 是的,这有点复杂。我必须对源代码进行大量挖掘才能完全理解它:-)
    猜你喜欢
    • 1970-01-01
    • 2011-10-16
    • 1970-01-01
    • 2021-12-31
    • 2013-10-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-18
    相关资源
    最近更新 更多