Often in a Jersey application, you need configuration properties that need to be applied dynamically. In this article I will describe some native capabilities in Jersey, how we can extend that native capability into make injectable properties, and finally I will explain how to use a small module I created for working with properties.
Table of Contents
Native Capabilities
JAX-RS contains a class javax.ws.rs.core.Configuration
from which we can obtain arbitrary properties from, that can be compiled from different locations. With Jersey, these properties can come from either servlet init-params or from explicitly set properties from a ResourceConfig
or Application
subclass. In a ResourceConfig
, we could just call the property(key, value)
method. For example
public class AppConfig extends ResourceConfig {
public AppConfig() {
property(PROP_KEY, PROP_VALUE);
}
}
Or in an Application
(only JAX-RS 2.0) subclass
public MyApplication extends Application {
@Override
public Map<String, Object> getProperties() {
Map<String, Object> props = new HashMap<>();
props.put(PROP_KEY, PROP_VALUE);
return props;
}
}
Either way, there are a countless number of ways you could populate the configuration properties. For instance you can read a .properties
file, and fill the properties with the contents of the Properties
object.
Once you’ve registered your properties, you can inject the above mentioned Configuration
instance into your resource classes or providers. For example
@Path("config")
public class ConfigResource {
@Context
private Configuration configuration;
@GET
public String getConfigProp() {
return (String)configuration.getProperty(CONFIG_PROP);
}
}
You can see a complete test case of the above example in this GitHub Gist
Custom Injection of Properties
What if we want to avoid having to repetitively get the property from the Configuration
object? Say we want to just inject the property value, maybe with a custom annotation. That is possible with the use of an (HK2) InjectionResolver
. For example you can do something like
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public static @interface Config {
String value();
}
public class ConfigInjectionResolver implements InjectionResolver<Config> {
@Context
private Configuration configuration;
@Override
public Object resolve(Injectee injectee, ServiceHandle<?> handle) {
if (String.class == injectee.getRequiredType()) {
Config annotation = injectee.getParent().getAnnotation(Config.class);
if (annotation != null) {
String prop = annotation.value();
return (String)configuration.getProperty(prop);
}
}
return null;
}
...
}
Then register the InjectionResolver
with HK2
ResourceConfig config = new ResourceConfig();
config.register(new AbstractBinder(){
@Override
protected void configure() {
bind(ConfigInjectionResolver.class)
.to(new TypeLiteral<InjectionResolver<Config>>(){})
.in(Singleton.class);
}
});
This will now allow us to inject the property as a String value into our resource class or provider.
@Path("config")
public class ConfigResource {
@Config(PROP_KEY)
private String propValue;
@GET
public String getConfigProp() {
return propValue;
}
}
You can see a complete runnable test case at this GitHub Gist.
One limitation of the above implementation is that it doesn’t allow for method parameter injection. This is not so much that there is anything wrong with the implementation, it is about the way the Jersey handles parameter injection. To read more about it, see Custom Method Parameter Injection in Jersey 2. Usually though, there is not much need to inject configuration properties into method parameters.
Note: Another thing about the above
InjectionResolver
implementation is that it does not support constructor injection. To see a complete example that does support this, see this Stack Overflow post.
jersey-properties Module
I created a small module that helps with working with configuration properties. You can find the project at GitHub, where there you can read more about about it. To use it you need to add this Maven artifact
<dependency>
<groupId>com.github.psamsotha</groupId>
<artifactId>jersey-properties</artifactId>
<version>0.1.1</version>
<dependency>
To use it at it’s most basic form, you should register the JerseyPropertiesFeature
, along with specifying a properties file from where it can obtain the properties.
public class AppConfig extends ResourceConfig {
public AppConfig() {
register(JerseyPropertiesFeature.class);
property(JerseyPropertiesFeature.RESOURCE_PATH, "app.properties");
}
}
With this basic configuration, if your app.properties
contained the following
some.prop=Some value
you would be able to do any of the following injections
@Path("test")
public class SomeResource {
@Prop("some.prop")
private String someFieldProp;
private String someConstructorProp;
public SomeResource(@Prop("some.prop") String someConstructorProp) {
this.someConstructorProp = someConstructorProp;
}
@GET
public String get(@Prop("some.prop") String someParamProp) {
return someParamProp;
}
}
Aside from this basic set up, you can also implement a PropertiesProvider
, that can be registered, and this where the properties would come from.
public class MorePropertiesProvider implements PropertiesProvider {
private final Map<String, String> moreProps = new HashMap<String, String>();
public MorePropertiesProvider() {
moreProps.put(PROP_ONE_KEY, PROP_ONE_VALUE);
}
@Override
public Map<String, String> getProperties() {
return moreProps;
}
}
Then just register the provider in the JerseyPropertiesFeature
public AppConfig() {
register(JerseyPropertiesFeature(new MorePropertiesProvider());
property(JerseyPropertiesFeature.RESOURCE_PATH, "app.properties");
}
With the PropertiesProvider
, you are able to easily extend this feature to get property from some other way, say from a yaml file.
To read more about this module, please read the README in the GitHub project, linked above. Also check out the tests in the project for some other use cases.
Support Me
If you found any information in this post useful, please show your support and like it, share it, tweet it, pin it, and/or plus one it. Much thanks!