使用inbuild注入的Jersey自定义方法参数注入 [英] Jersey custom method parameter injection with inbuild injection

查看:480
本文介绍了使用inbuild注入的Jersey自定义方法参数注入的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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 need some information so to parse that information I defined a custom filter like below

@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

I haven't actually implemented the method but structure is above. There is also a 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;
    }
}

现在在我的资源方法中我正在做如下

Now in my resource method I am doing like below

@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;
}

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

Here is how I am configuring the application

@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);
        }
    });
}

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

The problem is during application start I am getting below error

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.

并且有很长的堆栈错误堆栈和描述

and there is very long stack error stack and description

以下是用户pojo的声明签名

Below is the declaration signature of user pojo

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

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

No annotations on User class. Model is a class that defines only single property id of type long and also no annotations on model class

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

When I remove the User parameter from above create resource method it works fine and when I removed TenantParam it also works fine. The problem only occurs when I use both User and TenantParam


  1. 我在这里缺少什么?如何解决这个错误?

编辑

我刚试过两个自定义方法参数注入,也无法正常工作

I just tried with two custom method param injection, that is also not working

@POST
@Path("/login")
@Timed
public void validateUser(@AuthParam AuthToken token, @TenantParam Tenant tenant) {


}




  1. 我在这里缺少什么?这是球衣的限制吗?


推荐答案

方法参数的处理方式略有不同。我们需要为此实现的组件是 ValueFactoryProvider 。一旦实现了,你还需要将它绑定在 AbstractBinder 中。

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有一个模式,它遵循用于实现 ValueFactoryProvider 。这是用于处理 @PathParam @QueryParam 等参数的模式。 Jersey为每一个以及其他人提供了 ValueFactoryProvider

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.

模式如下:


  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 提供方法参数值。

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

InjectionResolver 是用于处理自定义注释的内容。使用这种模式,而不是像OP那样直接实现它,我们只是扩展 ParamInjectionResolver 传入我们的 AbstractValueFactoryProvider 实现class to super constructor

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<TenantParam> {

    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<InjectionResolver<TenantParam>>(){})
                .in(Singleton.class);
        bind(TenantValueProvider.class)
                .to(ValueFactoryProvider.class)
                .in(Singleton.class);
    }
}

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

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
 * 
 *  <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));
    }
}






另请参阅:

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

这篇关于使用inbuild注入的Jersey自定义方法参数注入的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆