在 ResourceConfig 中启动 jersey 依赖解析器
Initiate jersey dependency resolver inside ResourceConfig
在 jetty/jersey2 自托管应用程序中 api 端点是在 ApiServiceConfig 中以编程方式生成的 class
ConfigurationProperties
class 读取属性文件并将其加载到 java.util.Properties
class.
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);
}
}
}
获取控制器
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
class:
中注册
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:
的帮助下实例化了一个 class
IController controller = this.objectMapper.convertValue(jsonNode.get("process"), AbstractBaseController.class);
这个 class 应该被注入另一个 class。创建 controller
实例时,解析器 DependencyInjectionBinder 未启动。
如果我将 DependencyInjectionBinder 实例化作为第一个操作移动到 ApiServiceConfiguration 构造函数中,则无论如何都不会将 属性 注入 controller
实例。
然而,当我注册一个 class 定义的端点时:
resourceConfig.register(AnEndpointClass.class);
DI 解析器启动并添加依赖项。
如何在以编程方式创建和注册端点时使依赖解析器为实例化的 classes 工作?
要显式注入对象,您需要获取 ServiceLocator
,然后调用 locator.inject(controller)
。您可以在 Feature
中获取 ServiceLocator
,如 .
中所述
并且由于您还需要使用控制器注册资源,因此您还需要一种在 Feature
中注册资源的方法。为此,您可以使用 ModelProcessor
。您可以在 Jersey documentation 中阅读更多相关信息。它允许您更改 Jersey 的资源模型。在这里我们可以只注册我们以编程方式构建的所有资源。
下面是使用 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
*
* 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"));
}
}
在 jetty/jersey2 自托管应用程序中 api 端点是在 ApiServiceConfig 中以编程方式生成的 class
ConfigurationProperties
class 读取属性文件并将其加载到 java.util.Properties
class.
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);
}
}
}
获取控制器
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
class:
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);
这个 class 应该被注入另一个 class。创建 controller
实例时,解析器 DependencyInjectionBinder 未启动。
如果我将 DependencyInjectionBinder 实例化作为第一个操作移动到 ApiServiceConfiguration 构造函数中,则无论如何都不会将 属性 注入 controller
实例。
然而,当我注册一个 class 定义的端点时:
resourceConfig.register(AnEndpointClass.class);
DI 解析器启动并添加依赖项。
如何在以编程方式创建和注册端点时使依赖解析器为实例化的 classes 工作?
要显式注入对象,您需要获取 ServiceLocator
,然后调用 locator.inject(controller)
。您可以在 Feature
中获取 ServiceLocator
,如
并且由于您还需要使用控制器注册资源,因此您还需要一种在 Feature
中注册资源的方法。为此,您可以使用 ModelProcessor
。您可以在 Jersey documentation 中阅读更多相关信息。它允许您更改 Jersey 的资源模型。在这里我们可以只注册我们以编程方式构建的所有资源。
下面是使用 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
*
* 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"));
}
}