Using a custom hk2 InjectionResolver to inject app

2019-02-18 03:01发布

问题:

Kind of a follow up to my previous question. I'm trying to inject application configuration data using JSR-330 standard annotations and the HK2 framework bundled with jersey.

Ideally I'd like to create a custom InjectionResolver for the Named annotation, which will lookup the desired values in a Map or Properties object that I will populate from data read elsewhere. In my first attempt I've created an Application instance like

public class MyApplication extends ResourceConfig {
    ...
    packages(MY_PACKAGES);
    property(MY_CONFIG_PROPERTY, someValue);
    register(new AbstractBinder() {
        @Override
        protected void configure() {
            bind(ConfigurationInjectionResolver.class)
            .to(new TypeLiteral<InjectionResolver<Named>>(){})
            .in(Singleton.class)
        }
    });
}

and then my InjectionResolver looks like

public class ConfigurationInjectionResolver implements InjectionResolver<Named> {
    @Context Application application;

    @Override
    public Object resolve(Injectee injectee, ServiceHandle<?> serviceHandle) {
        // lookup data in application.getProperties();
    }
}

My problem is that application.getProperties() is empty. Any idea what's wrong? Also, could I bind an instance of my Injector instead of binding the class? That way I could construct the instance passing my Map data as a parameter.

回答1:

"My problem is that application.getProperties() is empty. Any idea what's wrong?

No. This actually works perfectly fine for me.

public class ConfigurationInjectionResolver implements InjectionResolver<Named> {  
    @Context
    Application application;

    @Override
    public Object resolve(Injectee injectee, ServiceHandle<?> root) {
        Named annotation = injectee.getParent().getAnnotation(Named.class);
        Map<String, Object> props = application.getProperties();
        String name = annotation.value();
        System.out.println(props.get(name));
        return props.get(name);
    }

    @Override
    public boolean isConstructorParameterIndicator() { return false; }
    @Override
    public boolean isMethodParameterIndicator() { return false; }  
}

@ApplicationPath("/rest")
public class JerseyApplication extends ResourceConfig {

    public JerseyApplication() {
        packages("jersey.startup.test");
        property("hello.config", "Hello World Property");
        register(new AbstractBinder() {
            @Override
            protected void configure() {
                bind(ConfigurationInjectionResolver.class)
                        .to(new TypeLiteral<InjectionResolver<Named>>() {
                        }).in(Singleton.class);
            }
        });
    }
}

Resource

@Path("/config")
public class ConfigResource {

    @Named("hello.config")
    String hello;

    @GET
    public Response getHello() {
        return Response.ok(hello).build();
    }
}

C:\>curl http://localhost:8080/test/rest/config
Hello World Property

Personally though, in this situation, I would create my own annotation, as to not override any existing functionality of the @Named annotation.


Another cool option

HK2 has a configuration extension, where you can load a Properties object from say a .properties file and and have those properties automatically injected with the @Configured annotation. I couldn't find any documentation on this, but there is an example usage of it in the HK2 source code examples.

Here's an example implementation

Required dependencies. Check the Jersey version and see what HK2 version it depends on. In my case Jersey 2.13 uses HK2 2.3.0-b10, so that should be the ${hk2.version}

<dependency>
    <groupId>org.glassfish.hk2</groupId>
    <artifactId>hk2-configuration-hub</artifactId>
    <version>${hk2.version}</version>
</dependency>
<dependency>
    <groupId>org.glassfish.hk2</groupId>
    <artifactId>hk2-configuration-integration</artifactId>
    <version>${hk2.version}</version>
</dependency>
<dependency>
    <groupId>org.glassfish.hk2</groupId>
    <artifactId>hk2-property-file</artifactId>
    <version>${hk2.version}</version>
</dependency>

App config

@ApplicationPath("/rest")
public class JerseyApplication extends ResourceConfig {

    @Inject
    public JerseyApplication(ServiceLocator locator) {
        packages("jersey.startup.test");
        ServiceLocatorUtilities.addClasses(locator, ConfigResource.class);
        try {
            loadConfigurationProperties(locator);
        } catch (IOException ex) {
            Logger.getLogger(JerseyApplication.class.getName())
                                   .log(Level.SEVERE, null, ex);
        }
    }

    private void loadConfigurationProperties(ServiceLocator locator) 
                                                 throws IOException {
        ConfigurationUtilities.enableConfigurationSystem(locator);
        PropertyFileUtilities.enablePropertyFileService(locator);
        PropertyFileService propertyFileService 
                = locator.getService(PropertyFileService.class);
        Properties props = new Properties();
        URL url = getClass().getResource("/configuration.properties");
        props.load(url.openStream());
        PropertyFileHandle propertyFileHandle 
                = propertyFileService.createPropertyHandleOfAnyType();
        propertyFileHandle.readProperties(props);
    }
}

configuration.properties

AppConfiguration.App.hello=Hello Squirrel Property!

Resource

@Path("/config")
@ConfiguredBy("AppConfiguration")
public class ConfigResource {

    @Configured
    String hello;

    @GET
    public Response getHello() {
        return Response.ok(hello).build();
    }
}

C:\>curl http://localhost:8080/test/rest/config
Hello Squirrel Property!

Diclaimer: Since this feature isn't well documented, I am not sure if I have a good implementation here. It is just by trial and error. For instance this

ServiceLocatorUtilities.addClasses(locator, ConfigResource.class);

I feel shouldn't be necessary. It seems redundant, as I am already package scanning. So to explicitly add the ConfigResource to the locator context doesn't seem right to me.