Java / Jersey – 使用ParamInjectionResolver创建自己的注入解析器 – 奇怪的行为

我正在尝试创建一个注射旋转变压器。 我有一个数据类:

public class MyData { ... } 

我有以下注释:

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

我的注射解析器看起来像这样:

 public class MyDataInjectionResolver extends ParamInjectionResolver { 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>() {}).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 ? ( 更新 )当参数不是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(); } } 

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

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

  1. 是一种原始类型
  2. 有一个接受单个String参数的构造函数
  3. 有一个名为valueOffromString的静态方法接受单个String参数(例如,参见Integer.valueOf(String)
  4. 有一个ParamConverterProvider JAX-RS扩展SPI的注册实现,它返回一个ParamConverter实例,该实例能够为该类型进行“from string”转换。
  5. Be ListSetSortedSet ,其中T满足上面的2,3或4。 生成的集合是只读的。

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

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

请注意, Dummy类不符合上面列出的任何要求。 运行应用程序时,应该在启动时出现exception,导致应用程序失败。 例外就是这样的

 ModelValidationException: No injection source for parameter ... 

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

好。 那么这一切与你的问题有什么关系呢? 好吧,所有参数注入都需要ValueFactoryProvider才能为它提供值。 如果没有,则无法在运行时创建参数。 因此,Jersey通过检查是否存在返回FactoryValueFactoryProvider来validation参数。 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注释的。

那么在启动validation期间会发生什么,对于每个参数,Jersey将遍历注册的每个ValueFactoryProvider以查看是否有可以处理该参数的值。 它能知道的唯一方法是它是否调用createValueFactory方法。 如果有一个返回Factory ,那么它就是成功的。 如果遍历了所有ValueFactoryProvider并且它们都返回null,则模型无效,我们将获得模型validationexception。 应该注意的是,有一堆内部ValueFactoryProvider用于注释注释,如@QueryParam@PathParam等注释。