How to parse request parameter (query and path par

2019-08-28 17:55发布

问题:

I have a rest API (PUT verb) which accepts both request body and path params:

Ex:

curl --data {a:1, b:2} -X PUT "https://example.com/users/{username}/address/{addressname}"

I am trying to fetch both request body and path param in one POJO

Response myAPI(@BeanParam Users user){
   system.out.println(user.username);
   system.out.println(user.a);

Users class

public class Users{

    @PathParam(username)
    private String userName;
    ......

    private String a;
  ......
}

But I am getting value of user.a as null. How to parse both request body and param in same class?

回答1:

You can do this with a custom annotation and an InjectionResolver. What the InjectionResolver does is allow you to create a custom injection point with your own annotation. So you could do something like

public class Users {

    @PathParam(username)
    private String userName;

    @Body
    private String a;
}

When implementing the InjectionResolver, you would grab the actual body from the ContainerRequest using the ContainerRequest#readEntity(Class) method. You would determine the Class to pass by doing some reflection on the Field that you can obtain inside the InjectionResolver. Below is a complete example using Jersey Test Framework. Run it 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.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;

import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.BeanParam;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Response;

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.server.ContainerRequest;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;

/**
 * Only one required dependency to run this test. Note this is using 2.25.1.
 * If you are using 2.26 or later, the implementation will be different,
 * and this will not work. You need to use the Jersey packaged `InjectionResolver`,
 * not the HK2 one. And you will also need to bind that `InjectionResolver`
 * to `GenericType` instead of `TypeLiteral` in the `AbstractBinder#configure()`.
 *
 * <dependency>
 *   <groupId>org.glassfish.jersey.test-framework.providers</groupId>
 *   <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
 *   <version>2.25.1</version>
 *   <scope>test</scope>
 * </dependency>
 */
public class BeanParamTest extends JerseyTest {

    @Path("test")
    @Consumes("application/json")
    @Produces("application/json")
    public static class TestResource {

        @POST
        @Path("{username}")
        public String post(@BeanParam ModelBean bean) {
            return bean.toString();
        }
    }

    @Override
    public ResourceConfig configure() {
        return new ResourceConfig(TestResource.class)
                .register(new AbstractBinder() {
                    @Override
                    protected void configure() {
                        bind(BodyInjectionResolver.class)
                                .to(new TypeLiteral<InjectionResolver<Body>>() {})
                                .in(Singleton.class);
                    }
                });
    }

    @Test
    public void testIt() {
        final Response res = target("test/peeskillet")
                .request()
                .post(Entity.json("{\"foo\":\"bar\"}"));
        System.out.println(res.readEntity(String.class));
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.FIELD})
    public @interface Body {}

    public static class ModelBean {

        @PathParam("username")
        private String username;

        @Body
        private String body;

        public String getUsername() {
            return username;
        }

        public void setUsername(String username) {
            this.username = username;
        }

        public String getBody() {
            return body;
        }

        public void setBody(String body) {
            this.body = body;
        }

        @Override
        public String toString() {
            return "ModelBean{" +
                    "username='" + username + '\'' +
                    ", body='" + body + '\'' +
                    '}';
        }
    }

    public static class BodyInjectionResolver implements InjectionResolver<Body> {

        @Inject
        private javax.inject.Provider<ContainerRequest> requestProvider;

        @Override
        public Object resolve(Injectee injectee, ServiceHandle<?> serviceHandle) {
            if (injectee.getParent().isAnnotationPresent(Body.class)) {
                AnnotatedElement parent = injectee.getParent();
                if (parent instanceof Field) {
                    Class<?> entityType = ((Field) parent).getType();
                    return requestProvider.get().readEntity(entityType);
                }

            }
            return null;
        }

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

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