Java/Jersey - 使用 ParamInjectionResolver 创建自己的注入解析器 - 奇怪的行为
Java/Jersey - creating own injection resolver with ParamInjectionResolver - strange behavior
我正在尝试创建注入解析器。我有一个数据 class:
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) 当参数不是 class 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 使用这个模型来处理请求。该模型的一部分由所有资源方法及其所有参数组成。更进一步,Jersey 还将验证模型以确保它是一个有效模型。模型中的某些无效内容可能会导致 Jersey 在 运行 时间内无法处理该模型。所以这个验证是为了保护我们。
也就是说,验证过程的一部分是验证方法参数。有一些规则可以控制我们可以将什么作为参数。例如,一个 @QueryParam
参数必须满足 the javadoc 中提到的要求之一:
- 是原始类型
- 有一个接受单个字符串参数的构造函数
- 有一个名为
valueOf
或 fromString
的静态方法,它接受一个字符串参数(例如,参见 Integer.valueOf(String)
)
- 有一个
ParamConverterProvider
JAX-RS 扩展 SPI 的注册实现,return 是一个 ParamConverter
实例,能够对类型进行 "from string" 转换。
- 为
List<T>
、Set<T>
或SortedSet<T>
,其中T
满足上述2、3或4。生成的集合是只读的。
您可以尝试一下。添加一个@QueryParam
使用以下任意class
public class Dummy {
public String value;
}
@GET
public Response get(@QueryParam("dummy") Dummy dummy) {}
请注意 Dummy
class 不满足上面列出的任何要求。当您 运行 应用程序时,您应该在启动时遇到异常,导致应用程序失败。例外情况类似于
ModelValidationException: No injection source for parameter ...
这意味着模型验证失败,因为 Jersey 不知道如何从查询参数创建 Dummy
实例,因为它不遵循允许的规则。
好的。那么这一切与您的问题有什么关系呢?好吧,所有的参数注入都需要一个 ValueFactoryProvider
才能为其提供一个值。如果没有,则参数将无法在 运行 时创建。因此,Jersey 通过检查 ValueFactoryProvider
是否存在 return 来验证参数 Factory
。 Jersey在运行时调用获取Factory
的方法,就是你说的那个:createValueFactory
.
现在请记住,当我们实现 createValueFactory
时,我们可以 return a Factory
或者 return 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
方法。如果有一个return是一个Factory
,那就是成功了。如果遍历完所有的ValueFactoryProvider
,都return为null,那么这个模型是无效的,我们会得到模型验证异常。需要注意的是,参数内部有一堆ValueFactoryProvider
,用@QueryParam
、@PathParam
等注解
我正在尝试创建注入解析器。我有一个数据 class:
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) 当参数不是 classMyData
时,它甚至会被调用,参见下面的第二个变体。
(*) 没有 @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 使用这个模型来处理请求。该模型的一部分由所有资源方法及其所有参数组成。更进一步,Jersey 还将验证模型以确保它是一个有效模型。模型中的某些无效内容可能会导致 Jersey 在 运行 时间内无法处理该模型。所以这个验证是为了保护我们。
也就是说,验证过程的一部分是验证方法参数。有一些规则可以控制我们可以将什么作为参数。例如,一个 @QueryParam
参数必须满足 the javadoc 中提到的要求之一:
- 是原始类型
- 有一个接受单个字符串参数的构造函数
- 有一个名为
valueOf
或fromString
的静态方法,它接受一个字符串参数(例如,参见Integer.valueOf(String)
) - 有一个
ParamConverterProvider
JAX-RS 扩展 SPI 的注册实现,return 是一个ParamConverter
实例,能够对类型进行 "from string" 转换。 - 为
List<T>
、Set<T>
或SortedSet<T>
,其中T
满足上述2、3或4。生成的集合是只读的。
您可以尝试一下。添加一个@QueryParam
使用以下任意class
public class Dummy {
public String value;
}
@GET
public Response get(@QueryParam("dummy") Dummy dummy) {}
请注意 Dummy
class 不满足上面列出的任何要求。当您 运行 应用程序时,您应该在启动时遇到异常,导致应用程序失败。例外情况类似于
ModelValidationException: No injection source for parameter ...
这意味着模型验证失败,因为 Jersey 不知道如何从查询参数创建 Dummy
实例,因为它不遵循允许的规则。
好的。那么这一切与您的问题有什么关系呢?好吧,所有的参数注入都需要一个 ValueFactoryProvider
才能为其提供一个值。如果没有,则参数将无法在 运行 时创建。因此,Jersey 通过检查 ValueFactoryProvider
是否存在 return 来验证参数 Factory
。 Jersey在运行时调用获取Factory
的方法,就是你说的那个:createValueFactory
.
现在请记住,当我们实现 createValueFactory
时,我们可以 return a Factory
或者 return 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
方法。如果有一个return是一个Factory
,那就是成功了。如果遍历完所有的ValueFactoryProvider
,都return为null,那么这个模型是无效的,我们会得到模型验证异常。需要注意的是,参数内部有一堆ValueFactoryProvider
,用@QueryParam
、@PathParam
等注解