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中提到的要求之一:
- 是一种原始类型
- 有一个接受单个String参数的构造函数
- 有一个名为
valueOf
或fromString
的静态方法接受单个String参数(例如,参见Integer.valueOf(String)
) - 有一个
ParamConverterProvider
JAX-RS扩展SPI的注册实现,它返回一个ParamConverter
实例,该实例能够为该类型进行“from string”转换。 - Be
List
,Set
或SortedSet
,其中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通过检查是否存在返回Factory
的ValueFactoryProvider
来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
等注释。