问题
I've been able to inject into my jersey resource from a filter as per How to inject an object into jersey request context?. This allows me to successfully inject into a method parameter:
@GET
public Response getTest(@Context MyObject myObject) { // this works
However, for setter/field/constructor injection, the HK2 Factory is invoked before the jersey filter, which means the provide() method returns null:
@Override
public MyObject provide() {
// returns null because the filter has not yet run,
// and the property has not yet been set
return (MyObject)context.getProperty("myObject");
}
Is there a way to define when the HK2 Factory will run so that it is invoke after the filter runs? If not, then the workaround is to define MyObject as an interface and define an additional implementation that takes a ContainerRequestContext in its constructor; any attempt to actually use the instance would then lazily delegate to the implementation that gets set on the ContainerRequestContext's property (presumably you wouldn't actually use the instance until after the filter runs -- at which point the property would be set).
But I would like to understand if it is possible to delay the point at which the HK2 Factory runs so that it runs after the filter (it already runs after the filter in the case of method parameter injection). If it is not possible, then I would like to understand if there is a fundamental reason why.
回答1:
Oddly it only works for me with @PreMatching
on the filter (which limits access to some things you may or may not need). Not quite sure what's going on under the hood, that cause it not to work without it :-(. Below is a complete test using Jersey Test Framework.
import java.io.IOException;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.PreMatching;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Context;
import javax.ws.rs.ext.Provider;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.process.internal.RequestScoped;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.internal.inject.AbstractContainerRequestValueFactory;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Assert;
import org.junit.Test;
public class FilterInjectionTest extends JerseyTest {
private static final String MESSAGE = "Inject OK";
private static final String OBJ_PROP = "myObject";
public static class MyObject {
private final String value;
public MyObject(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
@PreMatching
@Provider
public static class MyObjectFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext context) throws IOException {
MyObject obj = new MyObject(MESSAGE);
context.setProperty(OBJ_PROP, obj);
}
}
public static class MyObjectFactory
extends AbstractContainerRequestValueFactory<MyObject> {
@Override
@RequestScoped
public MyObject provide() {
return (MyObject) getContainerRequest().getProperty(OBJ_PROP);
}
@Override
public void dispose(MyObject t) {
}
}
@Path("method-param")
public static class MethodParamResource {
@GET
public String getResponse(@Context MyObject myObject) {
return myObject.getValue();
}
}
@Path("constructor")
public static class ConstructorResource {
private final MyObject myObject;
@Inject
public ConstructorResource(@Context MyObject myObject) {
this.myObject = myObject;
}
@GET
public String getResponse() {
return myObject.getValue();
}
}
@Path("field")
public static class FieldResource {
@Inject
private MyObject myObject;
@GET
public String getResponse() {
return myObject.getValue();
}
}
@Override
public Application configure() {
ResourceConfig config = new ResourceConfig();
config.register(MethodParamResource.class);
config.register(MyObjectFilter.class);
config.register(ConstructorResource.class);
config.register(FieldResource.class);
config.register(new AbstractBinder() {
@Override
protected void configure() {
bindFactory(MyObjectFactory.class)
.to(MyObject.class).in(Singleton.class);
}
});
return config;
}
@Test
public void methoParamInjectionOk() {
String response = target("method-param").request().get(String.class);
Assert.assertEquals(MESSAGE, response);
System.out.println(response);
}
@Test
public void costructorInjectionOk() {
String response = target("constructor").request().get(String.class);
Assert.assertEquals(MESSAGE, response);
System.out.println(response);
}
@Test
public void fieldInjectionOk() {
String response = target("field").request().get(String.class);
Assert.assertEquals(MESSAGE, response);
System.out.println(response);
}
}
UPDATE
The solution, without having to make it a @PreMatching
filter, is to inject with javax.inject.Provider. This will allow you to lazily retrieve the object. I guess what happens with the constructor and field injection is that right after matching the resource class, it it immediately created and injected. Because the filter hasn't been called yet, there is no object for the factory. It works for the method injection, because it is just like any other method call. The object is passed to it when the method is called. Below is the example with the javax.inject.Provider
@Path("constructor")
public static class ConstructorResource {
private final javax.inject.Provider<MyObject> myObjectProvider;
@Inject
public ConstructorResource(javax.inject.Provider<MyObject> myObjectProvider) {
this.myObjectProvider = myObjectProvider;
}
@GET
public String getResponse() {
return myObjectProvider.get().getValue();
}
}
@Path("field")
public static class FieldResource {
@Inject
private javax.inject.Provider<MyObject> myObjectProvider;;
@GET
public String getResponse() {
return myObjectProvider.get().getValue();
}
}
来源:https://stackoverflow.com/questions/30854855/hk2-factory-invoked-prior-to-jersey-filter-when-context-is-used-for-setter-fiel