问题
I am trying to create an injection resolver. I have a data class:
public class MyData {
...
}
I have the following annotation:
@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 ...
}
}
}
I am binding as following:
bind(MyDataValueFactoryProvider.class).to(ValueFactoryProvider.class).in(Singleton.class);
bind(MyDataInjectionResolver.class).to(new TypeLiteral<InjectionResolver<MyDataInject>>() {}).in(Singleton.class);
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();
}
}
- 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.
(*) 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();
}
}
Another variant:
@Path("test")
public class Test {
@GET
public Response test(@Valid SomeOtherClass data) {
return Response.status(Response.Status.OK).entity("Hello world!").build();
}
}
回答1:
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.
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:
- 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.
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) {}
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 ...
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.
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
.
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;
}
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
.
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.
来源:https://stackoverflow.com/questions/39635108/java-jersey-creating-own-injection-resolver-with-paraminjectionresolver-stra