Java/Jersey - 使用 ParamInjectionResolver 创建自己的注入解析器 - 奇怪的行为
我正在尝试创建一个注入解析器.我有一个数据类:
I am trying to create an injection resolver. I have a data class:
public class MyData {
...
}
我有以下注释:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyDataInject {
}
我的注入解析器如下所示:
My injection resolver looks like this:
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 资源进行测试:
I left the implementation of the actual factory out for brevity. Everything works fine, but I am noticing some behavior which I cannot explain. I am testing with the following JAX-RS resource:
@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
类时,它甚至会被调用,参见下面的第二个变体. - The first thing I notice is that
MyDataValueFactoryProvider.createValueFactory
is called twice during start-up. Why is that? This smells like some error. The good thing is that the factory is only accessed once when a client does a request. - Another observation is that if I remove the
@MyDataInject
annotation in the resource as below (*),MyDataValueFactoryProvider.createValueFactory
is still getting called. Why is that? This is odd, since it should be bound to only@MyDataInject
? (update) It is even called when the parameter is not of classMyData
, see second variant below. - 成为原始类型
- 有一个接受单个字符串参数的构造函数
- 有一个名为
valueOf
或fromString
的静态方法,它接受单个 String 参数(例如,参见Integer.valueOf(String)
) - 拥有一个已注册的
ParamConverterProvider
JAX-RS 扩展 SPI 实现,它返回一个能够对类型进行来自字符串"转换的ParamConverter
实例. - 为
List
、Set
或SortedSet
,其中T
满足2、3 或 4 以上.生成的集合是只读的. - Be a primitive type
- Have a constructor that accepts a single String argument
- Have a static method named
valueOf
orfromString
that accepts a single String argument (see, for example,Integer.valueOf(String)
) - Have a registered implementation of
ParamConverterProvider
JAX-RS extension SPI that returns aParamConverter
instance capable of a "from string" conversion for the type. - Be
List<T>
,Set<T>
orSortedSet<T>
, whereT
satisfies 2, 3 or 4 above. The resulting collection is read-only.
(*) 没有 @MyDataInject
注释的资源:
(*) Resource without @MyDataInject
annotation:
@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 在运行时无法处理该模型.所以这个验证是为了保护我们.
On startup, Jersey builds an internal model of all the resources. Jersey uses this model to process requests. Part of that model consists of all the resource methods and all of its parameters. To take it even further, Jersey will also validate the model to make sure it is a valid model. Something invalid in the model may cause Jersey not to be able to process that model during runtime. So this validation is there to protect us.
话虽如此,验证过程的一部分是验证方法参数.有一些规则可以控制我们可以拥有的参数.例如,@QueryParam
参数必须满足 javadoc:
That being said, part of the validation process is to validate the method parameters. There are rules that govern what we can have as parameters. For example, a @QueryParam
parameters must meet one of the requirements mentioned here in the javadoc:
这里有一些你可以尝试的东西.使用以下任意类添加 @QueryParam
Here's something you can try out. Add a @QueryParam
using the following arbitrary class
public class Dummy {
public String value;
}
@GET
public Response get(@QueryParam("dummy") Dummy dummy) {}
请注意,Dummy
类不满足上面列出的任何要求.当您运行应用程序时,您应该会在启动时收到异常,从而导致应用程序失败.例外情况类似于
Notice that the Dummy
class doesn't meet any of the requirements listed above. When you run the application, you should get an exception on startup, causing the application to fail. The exception will be something like
ModelValidationException: No injection source for parameter ...
这意味着模型验证失败,因为 Jersey 不知道如何从查询参数创建 Dummy
实例,因为它不遵循允许的规则.
This means that the validation of the model failed because Jersey has no idea how to create the Dummy
instance from the query param, as it doesn't follow the rules of what is allowed.
好的.那么这一切与您的问题有什么关系?好吧,所有参数注入都需要 ValueFactoryProvider
才能为其提供值.如果没有,则无法在运行时创建参数.因此 Jersey 通过检查是否存在返回 Factory
的 ValueFactoryProvider
来验证参数.Jersey 在运行时调用来获取 Factory
的方法,就是你提到的那个:createValueFactory
.
Ok. so how is this all related to your question? Well, all parameter injection requires a ValueFactoryProvider
to be able to provide a value for it. If there isn't one, then the parameter will not be able to be created at runtime. So Jersey validates the parameters by checking for the existence of a ValueFactoryProvider
that returns a Factory
. The method that Jersey calls to obtain the Factory
at runtime, is the one you mentioned: createValueFactory
.
现在请记住,当我们实现 createValueFactory
时,我们可以返回 Factory
也可以返回 null.我们应该如何实现它,是检查 Parameter
参数以查看我们是否可以处理该参数.比如
Now keep in mind that when we implement the createValueFactory
, we can either return a Factory
or we can return null. How we should implement it, is the check to Parameter
argument to see if we can handle that parameter. For instance
protected Factory<?> createValueFactory(Parameter parameter) {
if (parameter.getRawType() == Dummy.class
&& parameter.isAnnotationPresent(MyAnnoation.class)) {
return new MyFactory();
}
return null;
}
所以我们在这里告诉 Jersey 这个 ValueFactoryProvider
可以处理什么.在这种情况下,我们可以处理 Dummy
类型的参数,并且如果参数使用 @MyAnnotation
进行注释.
So here we are telling Jersey what this ValueFactoryProvider
can handle. In this case we can handle parameters of type Dummy
and if the parameter is annotated with @MyAnnotation
.
那么在启动验证期间会发生什么,对于每个参数,Jersey 会遍历每个注册的 ValueFactoryProvider
以查看是否有一个可以处理该参数.它可以知道的唯一方法是它是否调用了 createValueFactory
方法.如果有一个返回 Factory
,那么它是成功的.如果遍历了所有的ValueFactoryProvider
都返回null,那么模型是无效的,我们会得到模型验证异常.需要注意的是,有一堆内部的 ValueFactoryProvider
s 用于使用 @QueryParam
、@PathParam
等注释进行注释的参数.
So what happens during startup validation, for each parameter, Jersey will traverse each ValueFactoryProvider
registered to see if there is one that can handle that parameter. The only way it can know is if it calls the createValueFactory
method. If there is one that returns a Factory
, then it is a success. If all the ValueFactoryProvider
s are traversed and they all return null, then the model is not valid and we will get the model validation exception. It should be noted that there are a bunch of internal ValueFactoryProvider
s for parameter annotated with annotations like @QueryParam
, @PathParam
, etc.
相关文章