In a jetty/jersey2 self-hosted app api endpoints are generated programmatically inside ApiServiceConfig class
ConfigurationProperties
class reads and loads properties file into java.util.Properties
class.
Jetty server instantiation is done in the following way.
// 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;
}
Dependency resolver binder is registered in DependencyInjectionBinder
class:
public class DependencyInjectionBinder extends AbstractBinder {
@Override
protected void configure() {
bind(ConfigInjectionResolver.class)
.to(new TypeLiteral<InjectionResolver<Config>>() {})
.in(Singleton.class);
}
}
ConfigInjectionResolver implements InjectionResolver and resolve some logic.
ApiServiceConfig
in a loop goes through descriptions and creates endpoints. For each endpoint Resource builder is created, populated and resource is registered. During creation of an endpoint resource a class is instantiated with the help of jackson-databind:
IController controller = this.objectMapper.convertValue(jsonNode.get("process"), AbstractBaseController.class);
This class is supposed to get another class injected. Resolver DependencyInjectionBinder is not kicking in when the controller
instance is created.
If I move DependencyInjectionBinder instantiation into ApiServiceConfiguration constructor as a first operation, injection of a property into the controller
instance doesn't happen anyway.
However when I register a class defined endpoint:
resourceConfig.register(AnEndpointClass.class);
DI resolver kicks in and adds dependency.
How to make Dependency resolver work for instantiated classes while programmatically create and register endpoints?
答案 0 :(得分:1)
要明确注入对象,您需要暂停ServiceLocator
,然后调用locator.inject(controller)
。如this post中所述,您可以在ServiceLocator
内获得Feature
。
由于您还需要使用控制器注册资源,因此您还需要一种在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"));
}
}