泽西自定义方法参数注入内置注入

时间:2015-03-19 13:24:11

标签: java rest jersey-2.0 dropwizard hk2

您好我正在使用dropwizard构建一个应用程序,即在内部使用jersey 2.16作为REST API框架。

对于所有资源方法的整个应用程序,我需要一些信息,以便解析我定义的自定义过滤器的信息,如下所示

@java.lang.annotation.Target(ElementType.PARAMETER)
@java.lang.annotation.Retention(RetentionPolicy.RUNTIME)
public @interface TenantParam {
}

租户工厂定义如下

public class TenantFactory implements Factory<Tenant> {

    private final HttpServletRequest request;
    private final ApiConfiguration apiConfiguration;

    @Inject
    public TenantFactory(HttpServletRequest request, @Named(ApiConfiguration.NAMED_BINDING) ApiConfiguration apiConfiguration) {
        this.request = request;
        this.apiConfiguration = apiConfiguration;
    }

    @Override
    public Tenant provide() {
        return null;
    }

    @Override
    public void dispose(Tenant tenant) {

    }
}

我还没有真正实现该方法,但结构在上面。还有一个TenantparamResolver

public class TenantParamResolver implements InjectionResolver<TenantParam> {

    @Inject
    @Named(InjectionResolver.SYSTEM_RESOLVER_NAME)
    private InjectionResolver<Inject> systemInjectionResolver;

    @Override
    public Object resolve(Injectee injectee, ServiceHandle<?> serviceHandle) {
        if(Tenant.class == injectee.getRequiredType()) {
            return systemInjectionResolver.resolve(injectee, serviceHandle);
        }
        return null;
    }

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

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

现在在我的资源方法中,我正如下面所做的那样

@POST
@Timed
public ApiResponse create(User user, @TenantParam Tenant tenant) {
    System.out.println("resource method invoked. calling service method");
    System.out.println("service class" + this.service.getClass().toString());
    //DatabaseResult<User> result = this.service.insert(user, tenant);
    //return ApiResponse.buildWithPayload(new Payload<User>().addObjects(result.getResults()));
    return null;
}

以下是我配置应用程序的方法

@Override
public void run(Configuration configuration, Environment environment) throws Exception {
    // bind auth and token param annotations
    environment.jersey().register(new AbstractBinder() {
        @Override
        protected void configure() {
            bindFactory(TenantFactory.class).to(Tenant.class);
            bind(TenantParamResolver.class)
                .to(new TypeLiteral<InjectionResolver<TenantParam>>() {})
                .in(Singleton.class);
        }
    });
}

问题是在应用程序启动期间我遇到错误

WARNING: No injection source found for a parameter of type public void com.proretention.commons.auth.resources.Users.create(com.proretention.commons.api.core.Tenant,com.proretention.commons.auth.model.User) at index 0.

并且存在非常长的堆栈错误堆栈和描述

以下是用户pojo的声明签名

公共类用户扩展com.company.models.Model {

用户类没有注释。 Model是一个类,它只定义long类型的单个属性id,并且在模型类

上也没有注释

当我从上面创建资源方法中删除User参数时,它工作正常,当我删除TenantParam时,它也可以正常工作。只有当我同时使用User和TenantParam

时才会出现此问题
  1. 我在这里缺少什么?如何解决这个错误?
  2. EDITED

    我刚尝试了两个自定义方法param注入,这也无法正常工作

    @POST
    @Path("/login")
    @Timed
    public void validateUser(@AuthParam AuthToken token, @TenantParam Tenant tenant) {
    
    
    }
    
    1. 我在这里缺少什么?这是球衣的限制吗?

1 个答案:

答案 0 :(得分:1)

对于注射,方法参数的处理方式略有不同。我们需要为此实现的组件是ValueFactoryProvider。实现后,您还需要将其绑定在AbstractBinder

泽西岛有一个模式,它遵循实施ValueFactoryProvider。这是用于处理@PathParam@QueryParam等参数的模式。泽西岛每个人以及其他人都有ValueFactoryProvider

模式如下:

  1. 我们不是直接实施ValueFactoryProvider,而是扩展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<Tenant>() {
    
                @Override
                public Tenant provide() {
                    ...
                }
            };
        }
    

    在这个组件中,它有一个我们需要实现的方法,它返回提供方法参数值的Factory

  2. InjectionResolver是用于处理自定义注释的内容。使用此模式,我们只需将ParamInjectionResolver实现类中的AbstractValueFactoryProvider扩展为超级构造函数

    ,而不是直接实现它。
    public static class TenantParamInjectionResolver 
            extends ParamInjectionResolver<TenantParam> {
    
        public TenantParamInjectionResolver() {
            super(TenantValueProvider.class);
        }
    } 
    
  3. 真的是这样。然后只需绑定两个组件

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

    以下是使用Jersey Test Framework的完整测试。所需的依赖项列在javadoc注释中。您可以像任何其他JUnit测试一样运行测试

    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
     * 
     *  <dependency>
     *      <groupId>org.glassfish.jersey.test-framework.providers</groupId>
     *      <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
     *      <version>2.22</version>
     *      <scope>test</scope>
     *  </dependency>
     *  <dependency>
     *      <groupId>org.glassfish.jersey.media</groupId>
     *      <artifactId>jersey-media-json-jackson</artifactId>
     *      <version>2.22</version>
     *      <scope>test</scope>
     *  </dependency>
     * 
     * @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<Tenant>() {
                    // 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<TenantParam> {
    
                public TenantParamInjectionResolver() {
                    super(TenantValueProvider.class);
                }
            } 
    
            public static class Binder extends AbstractBinder {
                @Override
                public void configure() {
                    bind(TenantParamInjectionResolver.class)
                            .to(new TypeLiteral<InjectionResolver<TenantParam>>(){})
                            .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));
        }
    }
    

    另见: