当使用@Context进行setter/field/constructor注入时,HK2工厂会在Jersey过滤器之前被调用。

11

根据如何将对象注入Jersey请求上下文中?,我已经能够从过滤器中注入到我的jersey资源中。这使得我能够成功地注入到方法参数中:

@GET
public Response getTest(@Context MyObject myObject) { // this works

然而,对于setter/field/constructor注入,HK2工厂在jersey过滤器之前被调用,这意味着provide()方法会返回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");
}

是否有一种方法来定义 HK2 工厂运行的时间,使其在过滤器运行被调用?如果没有,那么解决方法是将 MyObject 定义为接口,并定义一个额外的实现,该实现在其构造函数中获取 ContainerRequestContext;任何尝试实际使用该实例的操作都将惰性地委托给设置在 ContainerRequestContext 属性上的实现(假设在过滤器运行之后才会实际使用该实例--此时属性将被设置)。

但我想知道是否有可能延迟 HK2 工厂运行的时间,以便它在过滤器之后运行(在方法参数注入的情况下,它已经在过滤器之后运行)。如果不可能,那么我想知道是否存在根本原因。


目前还没有解决方案,但我刚意识到在字段和构造函数注入的情况下,Factory<MyObject>.provide()被调用的时间是在我的过滤器之前,这是错误的顺序——我需要provide()在过滤器运行之后运行。这就是为什么资源字段和资源构造函数参数都为空的原因。对于setter注入,Factory.provide()根本没有被调用,我的资源setter也没有被调用。方法参数注入正常工作:先运行过滤器,然后运行Factory.provide(),最后资源方法具有预期的非空参数值。 - rndgstn
setter 没有被调用是我的编码错误。我正在编辑原始问题以反映真正的问题,即 HK2 工厂在 Jersey 过滤器之前被调用。 - rndgstn
1个回答

13

奇怪的是,只有在过滤器上加上@PreMatching后才能正常工作(这会限制某些你可能需要或不需要的东西)。不太确定底层发生了什么事情,导致没有加上它就无法正常工作 :-(。下面是使用Jersey测试框架进行的完整测试。

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);
    }
}

更新

解决方法不需要将其作为@PreMatching过滤器,而是使用javax.inject.Provider进行注入。这样可以使您延迟检索对象。我猜构造函数和字段注入的情况是在匹配资源类之后立即创建并注入。因为还没有调用过滤器,所以工厂没有对象。对于方法注入,它与任何其他方法调用一样。当调用方法时,对象会传递给它。以下是使用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();
    }
}

1
谢谢,是的,这对我也有效。@PreMatching注释会导致过滤器更早地运行。这不完全是我想要的——我更喜欢延迟HK2工厂的执行——但这样做是有道理的,你指出了这一点很有帮助。仍然很好奇为什么除了方法参数注入的情况外,工厂在后匹配过滤器之前运行,并且是否可以调整此行为。 - rndgstn
1
请查看 javax.inject.Provider 的更新解决方案以解决问题。 - Paul Samsotha

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接