Dependency Injection With Jersey 2.0

Dependency injection with Jersey 2.0

You need to define an AbstractBinder and register it in your JAX-RS application. The binder specifies how the dependency injection should create your classes.

public class MyApplicationBinder extends AbstractBinder {
@Override
protected void configure() {
bind(MyService.class).to(MyService.class);
}
}

When @Inject is detected on a parameter or field of type MyService.class it is instantiated using the class MyService. To use this binder, it need to be registered with the JAX-RS application. In your web.xml, define a JAX-RS application like this:

<servlet>
<servlet-name>MyApplication</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>com.mypackage.MyApplication</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>MyApplication</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>

Implement the MyApplication class (specified above in the init-param).

public class MyApplication extends ResourceConfig {
public MyApplication() {
register(new MyApplicationBinder());
packages(true, "com.mypackage.rest");
}
}

The binder specifying dependency injection is registered in the constructor of the class, and we also tell the application where to find the REST resources (in your case, MyResource) using the packages() method call.

Jersey HK2 Dependency Injection

What you can do is create a custom annotation

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface Config {
String value();
}

Then create an InjectionResolver for it (which allows for injection using custom annotations)

public static class ConfigInjectionResolver implements InjectionResolver<Config> {

private static final Map<String, String> properties = new HashMap<>();

public ConfigInjectionResolver() {
properties.put("greeting.message", "Hello World");
}

@Override
public Object resolve(Injectee injectee, ServiceHandle<?> handle) {
if (String.class == injectee.getRequiredType()) {
AnnotatedElement elem = injectee.getParent();
if (elem instanceof Constructor) {
Constructor ctor = (Constructor) elem;
Config config = (Config) ctor.getParameterAnnotations()[injectee.getPosition()][0];
return properties.get(config.value());
} else {
Config config = elem.getAnnotation(Config.class);
return properties.get(config.value());
}
}
return null;
}

@Override
public boolean isConstructorParameterIndicator() { return true; }

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

This example just uses a Map, but I'm sure you can figure out how to make it use Properties. Once you register the InjectionResolver, you can now just do

public SomeService(@Config("some.property") String property) {}

Here is a complete test case

import org.glassfish.hk2.api.Injectee;
import org.glassfish.hk2.api.InjectionResolver;
import org.glassfish.hk2.api.ServiceHandle;
import org.glassfish.hk2.api.TypeLiteral;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.filter.LoggingFilter;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;

import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
import java.lang.annotation.*;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;

import static org.junit.Assert.*;

/**
* Run like any other JUnit Test. Only one required dependency
*
* <dependency>
* <groupId>org.glassfish.jersey.test-framework.providers</groupId>
* <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
* <version>${jersey2.version}</version>
* </dependency>
*
* @author Paul Samsotha
*/
public class ConfigExample extends JerseyTest {

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public static @interface Config {
String value();
}

public static class ConfigInjectionResolver implements InjectionResolver<Config> {

private static final Map<String, String> properties = new HashMap<>();

public ConfigInjectionResolver() {
properties.put("greeting.message", "Hello World");
}

@Override
public Object resolve(Injectee injectee, ServiceHandle<?> handle) {
if (String.class == injectee.getRequiredType()) {
AnnotatedElement elem = injectee.getParent();
if (elem instanceof Constructor) {
Constructor ctor = (Constructor) elem;
Config config = (Config) ctor.getParameterAnnotations()[injectee.getPosition()][0];
return properties.get(config.value());
} else {
Config config = elem.getAnnotation(Config.class);
return properties.get(config.value());
}
}
return null;
}

@Override
public boolean isConstructorParameterIndicator() { return true; }

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


private static interface GreetingService {
String getGreeting();
}

private static class ConfiguredGreetingService implements GreetingService {
private String message;

public ConfiguredGreetingService(@Config("greeting.message") String message) {
this.message = message;
}

@Override
public String getGreeting() {
return this.message;
}
}

@Path("greeting")
public static class GreetingResource {

@Inject
private GreetingService greetingService;

@GET
public String getConfigProp() {
return greetingService.getGreeting();
}
}

@Override
public ResourceConfig configure() {
ResourceConfig config = new ResourceConfig(GreetingResource.class);
config.register(new LoggingFilter(Logger.getAnonymousLogger(), true));
config.register(new AbstractBinder(){
@Override
protected void configure() {
bind(ConfiguredGreetingService.class).to(GreetingService.class).in(Singleton.class);
bind(ConfigInjectionResolver.class)
.to(new TypeLiteral<InjectionResolver<Config>>(){})
.in(Singleton.class);
}
});
return config;
}

@Test
public void should_get_configured_greeting() {
final Response response = target("greeting")
.request().get();
assertEquals("Hello World", response.readEntity(String.class));
}
}

Dependency Injection in jersey 2.17

In your second attempt (without the cdi dependencies; using HK2) your binding is incorrect. It should be

bind(Implementation).to(Contract)
// - i.e.
bind(DemoServiceImpl.class).to(DemoService.class);

You have it the other way around.

As far as testing, if you have the test in same package (in the test area of the project) you should be able to assign the service, as it's package private. Though personally, I've gotten into the habit of constructor injection. Another thing you can do is use Jersey Test Framework. You can see an complete example here, where a mock service is injected

Jersey HK2 Dependency Injection doesn't work after update to v2.27

Answer by Paul Samsotha in a comment:

Try to change your AbstractBinder import. There are two, a Jersey one and an HK2 one. Try to use the Jersey one.

Basically, I needed to change the AbstractBinder class I implemented from

org.glassfish.hk2.utilities.binding.AbstractBinder

to

org.glassfish.jersey.internal.inject.AbstractBinder

The difference is that Jersey decoupled HK2 from it's internal DI mechanism in version 2.26, and thus, I needed to use the new AbstractBinder import, which comes directly from Jersey, and not HK2.

There are a few API differences: for instance, instead of a Factory<T>, bindFactory() takes a java.util.function.Supplier<T>.

Jersey 2 inject dependencies into unit test

You can use the main IoC container, and just explicitly inject the test class. Jersey uses HK2 as its DI framework, and its IoC container is the ServiceLocator, which has a method inject(anyObject) that can inject any objects with dependencies that are in its registry.

For example you could do something like

public class InjectionTest {

@Inject
private TestController controller;

@Before
public void setUp() {
final Binder b = new AbstractBinder() {
@Override
public void configure() {
bindAsContract(TestController.class);
}
};
final ServiceLocator locator = ServiceLocatorUtilities.bind(new TestBinder(), b);
locator.inject(this);
}

@Test
public void doTest() {
assertNotNull(controller);
String response = controller.get();
assertEquals("Hello Tests", response);
}
}

The ServiceLocatorUtilities class is a helper class that allows us to easily create the ServiceLocator, and then we just call inject(this) to inject the InjectionTest.

If it seems repetitive to do this for all your controller tests, you may want to create an abstract base test class. Maybe something like

public abstract class AbstractControllerTest {

protected ServiceLocator locator;
private final Class<?> controllerClass;

protected AbstractControllerTest(Class<?> controllerClass) {
this.controllerClass = controllerClass;
}

@Before
public void setUp() {
final AbstractBinder binder = new AbstractBinder() {
@Override
public void configure() {
bindAsContract(controllerClass);
}
};
locator = ServiceLocatorUtilities.bind(new TestBinder(), binder);
locator.inject(this);
}

@After
public void tearDown() {
if (locator != null) {
locator.shutdown();
}
}
}

Then in your concrete class

public class TestControllerTest extends AbstractControllerTest {

public TestControllerTest() {
super(TestController.class);
}

@Inject
private TestController controller;

@Test
public void doTest() {
assertNotNull(controller);
assertEquals("Hello Tests", controller.get());
}
}

If you spent some more time, I'm sure you could come up with a better abstract test class design. It was the first thing that came to mind for me.

Note: For anything request scoped, you mayb need to just mock it. When running the unit tests, there is no request context, so the test will fail.

See Also:

  • Using Jersey's Dependency Injection in a Standalone application
  • HK2 documentation


Related Topics



Leave a reply



Submit