You don't need to use any BootstrapConfigurator
. All you need to is add the services to the injector and they will be added later to the list of value providers.
To configure it, you can still use the AbstractBinder
, but instead of the HK2 one, use the Jersey one. The ValueParamProvider
can still be bound the same way, but for the InjectionResolver
, you should make sure to implement not the HK2 resolver, but the Jersey one. Then instead of binding to TypeLiteral
, bind to GenericType
.
I just want to add that a misconception that people have when trying to implement parameter injection is that we also need an InjectResolver
to use a custom annotation for the method parameter. This is not the case. The method parameter annotation is just a marker annotation that we should check inside ValueParamProvider#getValueProvider()
method. An InjectResolver
is only needed for non-method-parameter injections, for instance field and constructor injection. If you don't need that, then you don't need the InjectionResolver
.
Below is a complete example using Jersey Test Framework. I didn't use an InjectionResolver
, just to show that it's not needed.
import org.glassfish.jersey.internal.inject.AbstractBinder;
import org.glassfish.jersey.server.ContainerRequest;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.model.Parameter;
import org.glassfish.jersey.server.spi.internal.ValueParamProvider;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import javax.inject.Singleton;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
import javax.ws.rs.core.Response;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.function.Function;
import static org.assertj.core.api.Assertions.assertThat;
public class ParamInjectTest extends JerseyTest {
@Target({ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Auth {
}
private static class User {
private String username;
public User(String username) {
this.username = username;
}
public String getUsername() {
return this.username;
}
}
public static class AuthValueParamProvider implements ValueParamProvider {
@Override
public Function<ContainerRequest, ?> getValueProvider(Parameter parameter) {
if (parameter.getRawType().equals(User.class)
&& parameter.isAnnotationPresent(Auth.class)) {
return new UserParamProvider();
}
return null;
}
private class UserParamProvider implements Function<ContainerRequest, User> {
@Override
public User apply(ContainerRequest containerRequest) {
return new User("Peeskillet");
}
}
@Override
public PriorityType getPriority() {
return Priority.HIGH;
}
}
public static class AuthFeature implements Feature {
@Override
public boolean configure(FeatureContext context) {
context.register(new AbstractBinder() {
@Override
protected void configure() {
bind(AuthValueParamProvider.class)
.to(ValueParamProvider.class)
.in(Singleton.class);
}
});
return true;
}
}
@Path("test")
@Consumes("text/plain")
public static class TestResource {
@POST
@Produces("text/plain")
public Response post(String text, @Auth User user) {
return Response.ok(user.getUsername() + ":" + text).build();
}
}
@Override
public ResourceConfig configure() {
return new ResourceConfig()
.register(TestResource.class)
.register(AuthFeature.class);
}
@Test
public void testIt() {
final Response response = target("test")
.request()
.post(Entity.text("Test"));
assertThat(response.getStatus()).isEqualTo(200);
assertThat(response.readEntity(String.class)).isEqualTo("Peeskillet:Test");
}
}
Another thing I'll mention is that in previous versions where you extended AbstractValueFactoryProvider
and implemented a ParamInjectionResolver
, most people did this to follow how Jersey implemented parameter injection while still allowing for other injection points (field and constructor). If you still want to use this pattern, you can.
Below is the AuthFeature
from the above test refactored
public static class AuthFeature implements Feature {
@Override
public boolean configure(FeatureContext context) {
InjectionManager im = InjectionManagerProvider.getInjectionManager(context);
AuthValueParamProvider authProvider = new AuthValueParamProvider();
im.register(Bindings.service(authProvider).to(ValueParamProvider.class));
Provider<ContainerRequest> request = () -> {
RequestProcessingContextReference reference = im.getInstance(RequestProcessingContextReference.class);
return reference.get().request();
};
im.register(Bindings.injectionResolver(new ParamInjectionResolver<>(authProvider, Auth.class, request)));
return true;
}
}
I figured this stuff out just digging through the source. All this configuration I saw in the ValueParamProviderConfigurator
. You don't need to implement your own ParamInjectionResolver
. Jersey has a concrete class already that we can just use, as done in the feature above.
If you change the TestResource
to inject by field, it should work now
@Path("test")
@Consumes("text/plain")
public static class TestResource {
@Auth User user;
@POST
@Produces("text/plain")
public Response post(String text) {
return Response.ok(user.getUsername() + ":" + text).build();
}
}