有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

ResourceConfig中的java依赖解析程序

在jetty/jersey2自托管应用程序中,api端点是在ApiServiceConfig类中以编程方式生成的 ConfigurationProperties类读取属性文件并将其加载到java.util.Properties类中

Jetty服务器实例化按以下方式完成

     // Create and register resources
    final ResourceConfig resourceConfig = new ApiServiceConfig()
            .register(new DependencyInjectionBinder());

    ServletContextHandler contextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);

    contextHandler.setContextPath("/mydomain/api");
    Server jettyServer = new Server(8585);
    jettyServer.setHandler(contextHandler);

    ServletHolder jerseyServlet = new ServletHolder(new ServletContainer(resourceConfig));
    contextHandler.addServlet(jerseyServlet, "/*");

    try {
        jettyServer.start();
        jettyServer.join();
    } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } finally {
        jettyServer.destroy();
    }

public class ApiServiceConfig extends ResourceConfig {
    public ApiServiceConfig() {
       for(JsonNode jsonNode: nodeArray) {
           // JSON endpoint service description example.
           //{
           //    "service": "/item/{id}",
           //    "method": "GET",
           //    "process": {
           //        "@type": "com.mycompany.projectx.endpoint.services.GetController",
           //        "uri_param": "id",
           //        "type": "item",
           //        "fields": "uuid,name,content_area,title,grade,dok,bloom,item_banks,...,item_banks_titles"
           //    }
           //}
           // Json property "service" describes a URL pattern for a request (eg. "/item/{id}").
           final String path = jsonNode.get("service").asText();

           // Api RESTful verb ('GET', 'POST', etc.)
           final String method = jsonNode.get("method").asText();

           // Map a process description of a service to specific controller implementation class.
           // This is the instance creation where I want injection to happen.
           IController controller = this.objectMapper.convertValue(jsonNode.get("process"), AbstractBaseController.class);
           // Controller is added to a HashMap 
           ...
           final Resource.Builder resourceBuilder = Resource.builder();
           resourceBuilder.path(path);
           final ResourceMethod.Builder methodBuilder = resourceBuilder.addMethod(method);

           methodBuilder.produces(new MediaType("text", "plain"))
               handledBy((Inflector)(ctx) -> {
                   // Controller is retrieved from the HashMap 
                   controller.execute(new ProcessEvent());
                   ...
                   return responseResult;
               });
           final Resource resource = resourceBuilder.build();
           registerResources(resource);
       }
    }
}

GetController

public class GetController extends AbstractBaseController {

    @Config("data.cassandra")
    String connectionString; // == null, but should be a string injected.

    public GetController() {
    }

    @Override
    public ProcessEvent process(ProcessEvent event) throws Exception {

       String uri_param = this.uri_param;
       event.contentType = "application/json";

       event.object = ".Get method of Item endpoint got executed. Cassandra IP: " + getApplicationProperties().getProperty("data.cassandra");

       return event;
   }

依赖项解析程序绑定器已在DependencyInjectionBinder类中注册:

public class DependencyInjectionBinder extends AbstractBinder {

    @Override
    protected void configure() {

        bind(ConfigInjectionResolver.class)
            .to(new TypeLiteral<InjectionResolver<Config>>() {})
            .in(Singleton.class);
    }
}

ConfigInjectionResolver实现InjectionResolver并解析一些逻辑

循环中的ApiServiceConfig遍历描述并创建端点。为每个端点创建、填充资源生成器并注册资源。在创建端点资源的过程中,会在jackson databind的帮助下实例化一个类:

IController controller = this.objectMapper.convertValue(jsonNode.get("process"), AbstractBaseController.class);

这门课应该再注射一门课。创建controller实例时,解析程序依赖注入绑定器未启动。 如果我将DependencyInjectionBinder实例化作为第一个操作移动到ApiServiceConfiguration构造函数中,那么将属性注入到controller实例中无论如何都不会发生

但是,当我注册一个类定义的端点时:

resourceConfig.register(AnEndpointClass.class);

DI解析器将起作用并增加依赖性

如何使依赖解析程序在以编程方式创建和注册端点的同时为实例化的类工作


共 (1) 个答案

  1. # 1 楼答案

    要显式地注入对象,需要获得ServiceLocator,并调用locator.inject(controller)。如this post中所述,可以在Feature中获取ServiceLocator

    由于还需要使用控制器注册资源,因此还需要一种在Feature中注册资源的方法。为此,您可以使用ModelProcessor。你可以在Jersey documentation中阅读更多关于它的信息。它允许你改变泽西岛的资源模型。在这里,我们可以注册所有以编程方式构建的资源

    下面是一个使用Jersey Test Framework的完整示例。您可以像运行任何其他JUnit测试一样运行它

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    import java.util.logging.Logger;
    
    import javax.annotation.Priority;
    import javax.inject.Singleton;
    import javax.ws.rs.container.ContainerRequestContext;
    import javax.ws.rs.core.Configuration;
    import javax.ws.rs.core.Feature;
    import javax.ws.rs.core.FeatureContext;
    import javax.ws.rs.core.Response;
    
    import org.glassfish.hk2.api.Injectee;
    import org.glassfish.hk2.api.InjectionResolver;
    import org.glassfish.hk2.api.ServiceHandle;
    import org.glassfish.hk2.api.ServiceLocator;
    import org.glassfish.hk2.api.TypeLiteral;
    import org.glassfish.hk2.utilities.binding.AbstractBinder;
    import org.glassfish.jersey.ServiceLocatorProvider;
    import org.glassfish.jersey.filter.LoggingFilter;
    import org.glassfish.jersey.process.Inflector;
    import org.glassfish.jersey.server.ResourceConfig;
    import org.glassfish.jersey.server.model.ModelProcessor;
    import org.glassfish.jersey.server.model.Resource;
    import org.glassfish.jersey.server.model.ResourceMethod;
    import org.glassfish.jersey.server.model.ResourceModel;
    import org.glassfish.jersey.test.JerseyTest;
    
    import org.junit.Test;
    import static org.hamcrest.CoreMatchers.is;
    import static org.junit.Assert.assertThat;
    
    /**
     * Stack Overflow https://stackoverflow.com/q/36410420/2587435
     * 
     * Run this like any other JUnit test. Only one required dependency
     * 
     *  <dependency>
     *      <groupId>org.glassfish.jersey.test-framework.providers</groupId>
     *      <artifactId>jersey-test-framework-provider-inmemory</artifactId>
     *      <version>${jersey2.version}</version>
     *      <scope>test</scope>
     *  </dependency>
     * 
     * @author Paul Samsotha
     */
    public class PropsInjectionTest extends JerseyTest {
    
        @Target(ElementType.FIELD)
        @Retention(RetentionPolicy.RUNTIME)
        public static @interface Config {
            String value();
        }
    
        @Singleton
        public static class ConfigInjectionResolver implements InjectionResolver<Config> {
    
            @Override
            public Object resolve(Injectee injectee, ServiceHandle<?> root) {
                if (String.class == injectee.getRequiredType()) {
                    Config anno = injectee.getParent().getAnnotation(Config.class);
                    if (anno != null) {
                        String key = anno.value();
                        return key + "Value";
                    }
                }
                return null;
            }
    
            @Override
            public boolean isConstructorParameterIndicator() { return false; }
    
            @Override
            public boolean isMethodParameterIndicator() { return false; }  
        }
    
    
        public static class Controller {
            @Config("Key")
            private String prop;
    
            public String getProp() {
                return prop;
            }
        }
    
        public static class ResourceFeature implements Feature {
    
            @Override
            public boolean configure(FeatureContext ctx) {
                final ServiceLocator locator = ServiceLocatorProvider.getServiceLocator(ctx);
                final Controller controller = new Controller();
                locator.inject(controller);
    
                final Resource.Builder builder = Resource.builder().path("test");
                final ResourceMethod.Builder methodBuilder = builder.addMethod("GET");
                methodBuilder.handledBy(new Inflector<ContainerRequestContext, String>(){
                    @Override
                    public String apply(ContainerRequestContext data) {
                        return controller.getProp();
                    }
                });
                final Resource resource = builder.build();
                ctx.register(new MyModelProcessor(resource));
                return true;      
            }
    
            @Priority(100)
            static class MyModelProcessor implements ModelProcessor {
                private final Resource[] resources;
    
                public MyModelProcessor(Resource... resources) {
                    this.resources = resources;
                }
    
                @Override
                public ResourceModel processResourceModel(ResourceModel rm, Configuration c) {
                    final ResourceModel.Builder builder = new ResourceModel.Builder(false);
                    // add any other resources not added in this feature. If there are none,
                    // you can skip this loop
                    for (Resource resource: rm.getResources()) {
                        builder.addResource(resource);
                    }
    
                    for (Resource resource: this.resources) {
                        builder.addResource(resource);
                    }
    
                    return builder.build();
                }
    
                @Override
                public ResourceModel processSubResource(ResourceModel rm, Configuration c) {
                    return rm;
                }
            }
        }
    
        @Override
        public ResourceConfig configure() {
            return new ResourceConfig()
                    .register(new ResourceFeature())           
                    .register(new LoggingFilter(Logger.getAnonymousLogger(), true))
                    .register(new AbstractBinder() {
                        @Override
                        protected void configure() {
                            bind(ConfigInjectionResolver.class)
                                    .to(new TypeLiteral<InjectionResolver<Config>>(){})
                                    .in(Singleton.class);
                        }
                    });
        }
    
        @Test
        public void allShouldBeGood() {
            final Response response = target("test").request().get();
            assertThat(response.readEntity(String.class), is("KeyValue"));
        }
    }