Jersey custom method parameter injection with inbuild injection

前端 未结 1 519

Hello I am building an application using dropwizard, that is using jersey 2.16 internally as REST API framework.

For the whole application on all resource methods I

1条回答
  •  粉色の甜心
    2021-01-05 18:47

    Method parameters are handled a little differently for injection. The component we need to implement for this, is the ValueFactoryProvider. Once you implement that, you also need to bind it in your AbstractBinder.

    Jersey has a pattern that it follows for implementing the ValueFactoryProvider. This is the pattern used to handle parameters like @PathParam and @QueryParam. Jersey has a ValueFactoryProvider for each one of those, as well as others.

    The pattern is as follows:

    1. Instead of implementing the ValueFactoryProvider directly, we extend AbstractValueFactoryProvider

      public static class TenantValueProvider extends AbstractValueFactoryProvider {
      
          @Inject
          public TenantValueProvider(MultivaluedParameterExtractorProvider mpep,
                                 ServiceLocator locator) {
              super(mpep, locator, Parameter.Source.UNKNOWN);
          }
      
          @Override
          protected Factory createValueFactory(Parameter parameter) {
              if (!parameter.isAnnotationPresent(TenantParam.class) 
                      || !Tenant.class.equals(parameter.getRawType())) {
                  return null;
              }
              return new Factory() {
      
                  @Override
                  public Tenant provide() {
                      ...
                  }
              };
          }
      

      In this component, it has a method we need to implement that returns the Factory that provides the method parameter value.

    2. The InjectionResolver is what is used to handle the custom annotation. With this pattern, instead of directly implementing it, as the OP has, we just extend ParamInjectionResolver passing in our AbstractValueFactoryProvider implementation class to super constructor

      public static class TenantParamInjectionResolver 
              extends ParamInjectionResolver {
      
          public TenantParamInjectionResolver() {
              super(TenantValueProvider.class);
          }
      } 
      

    And that's really it. Then just bind the two components

    public static class Binder extends AbstractBinder {
        @Override
        public void configure() {
            bind(TenantParamInjectionResolver.class)
                    .to(new TypeLiteral>(){})
                    .in(Singleton.class);
            bind(TenantValueProvider.class)
                    .to(ValueFactoryProvider.class)
                    .in(Singleton.class);
        }
    }
    

    Below is a complete test using Jersey Test Framework. The required dependencies are listed in the javadoc comments. You can run the test like any other JUnit test

    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.inject.Inject;
    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.Response;
    import org.glassfish.hk2.api.Factory;
    import org.glassfish.hk2.api.InjectionResolver;
    import org.glassfish.hk2.api.ServiceLocator;
    import org.glassfish.hk2.api.TypeLiteral;
    import org.glassfish.hk2.utilities.binding.AbstractBinder;
    import org.glassfish.jersey.filter.LoggingFilter;
    import org.glassfish.jersey.server.ContainerRequest;
    import org.glassfish.jersey.server.ResourceConfig;
    import org.glassfish.jersey.server.internal.inject.AbstractContainerRequestValueFactory;
    import org.glassfish.jersey.server.internal.inject.AbstractValueFactoryProvider;
    import org.glassfish.jersey.server.internal.inject.MultivaluedParameterExtractorProvider;
    import org.glassfish.jersey.server.internal.inject.ParamInjectionResolver;
    import org.glassfish.jersey.server.model.Parameter;
    import org.glassfish.jersey.server.spi.internal.ValueFactoryProvider;
    import org.glassfish.jersey.test.JerseyTest;
    import org.junit.Test;
    import static org.junit.Assert.assertEquals;
    
    /**
     * Stack Overflow https://stackoverflow.com/q/29145807/2587435
     * 
     * Run this like any other JUnit test. Dependencies required are as the following
     * 
     *  
     *      org.glassfish.jersey.test-framework.providers
     *      jersey-test-framework-provider-grizzly2
     *      2.22
     *      test
     *  
     *  
     *      org.glassfish.jersey.media
     *      jersey-media-json-jackson
     *      2.22
     *      test
     *  
     * 
     * @author Paul Samsotha
     */
    public class TenantInjectTest extends JerseyTest {
    
        @Target(ElementType.PARAMETER)
        @Retention(RetentionPolicy.RUNTIME)
        public static @interface TenantParam {
        }
    
        public static class User {
            public String name;
        }
    
        public static class Tenant {
            public String name;
            public Tenant(String name) {
                this.name = name;
            }
        }
    
        public static class TenantValueProvider extends AbstractValueFactoryProvider {
    
            @Inject
            public TenantValueProvider(MultivaluedParameterExtractorProvider mpep,
                                       ServiceLocator locator) {
                super(mpep, locator, Parameter.Source.UNKNOWN);
            }
    
            @Override
            protected Factory createValueFactory(Parameter parameter) {
                if (!parameter.isAnnotationPresent(TenantParam.class) 
                        || !Tenant.class.equals(parameter.getRawType())) {
                    return null;
                }
                return new AbstractContainerRequestValueFactory() {
                    // You can @Inject things here if needed. Jersey will inject it.
                    // for example @Context HttpServletRequest
    
                    @Override
                    public Tenant provide() {
                        final ContainerRequest request = getContainerRequest();
                        final String name 
                                = request.getUriInfo().getQueryParameters().getFirst("tenent");
                        return new Tenant(name);
                    }
                };
            }
    
            public static class TenantParamInjectionResolver 
                    extends ParamInjectionResolver {
    
                public TenantParamInjectionResolver() {
                    super(TenantValueProvider.class);
                }
            } 
    
            public static class Binder extends AbstractBinder {
                @Override
                public void configure() {
                    bind(TenantParamInjectionResolver.class)
                            .to(new TypeLiteral>(){})
                            .in(Singleton.class);
                    bind(TenantValueProvider.class)
                            .to(ValueFactoryProvider.class)
                            .in(Singleton.class);
                }
            }
        }
    
    
        @Path("test")
        @Produces("text/plain")
        @Consumes("application/json")
        public static class TestResource {
            @POST
            public String post(User user, @TenantParam Tenant tenent) {
                return user.name + ":" + tenent.name;
            }
        }
    
        @Override
        public ResourceConfig configure() {
            return new ResourceConfig(TestResource.class)
                    .register(new TenantValueProvider.Binder())
                    .register(new LoggingFilter(Logger.getAnonymousLogger(), true));
        }
    
        @Test
        public void shouldReturnTenantAndUserName() {
            final User user = new User();
            user.name = "peeskillet";
            final Response response = target("test")
                    .queryParam("tenent", "testing")
                    .request()
                    .post(Entity.json(user));
    
            assertEquals(200, response.getStatus());
            assertEquals("peeskillet:testing", response.readEntity(String.class));
        }
    }
    

    See Also:

    • Jersey 2.x Custom Injection Annotation With Attributes
    • My Comment in the Dropwizard issue: "No injection source found for a parameter"

    0 讨论(0)
提交回复
热议问题