【问题标题】:Merge two query params in one single object with JAX-RS使用 JAX-RS 将两个查询参数合并到一个对象中
【发布时间】:2015-11-03 15:48:38
【问题描述】:

我有一些资源处理方法,其中包含数十个@QueryParam 参数和@Default,大致按主题分组(分页/排序、过滤、身份验证)。这真的很麻烦,我想简化一下。好消息是这些参数按主题(分页、排序、过滤等)分组,因此我可以将整个参数集减少到 4 个方法。

我怎样才能做到这一点?

通常,我想来自这个:

@GET
public Response findAll(
    @QueryParam("sort") @DefaultValue("name") List<String> sort,
    @QueryParam("from") UUID fromId
) {
  // Validate sort
  // Validate fromId
}

到这里:

@GET
public Response findAll(@Context Pagination pagination) { // Inject pagination
  // Yeah, small code! Yeah, modularity!
}

// Create the pagination somewhere else.

public Pagination createPagination(@Context UriInfo uriInfo) {
  Optional<UUID> fromId = extractFromId(uriInfo); // retrieve "from" from uriInfo
  List<String> sort = extractSort(uriInfo); // retrieve "sort" from uriInfo
  Pagination pagination = new Pagination();
  pagination.setFromId(fromId);
  pagination.setSort(sort);
  // Validate pagination
  return pagination;
}

注意:正如我在示例中所展示的,我不介意自己编写更多代码,但我无法忍受在我的方法中包含太多参数并阅读@QueryParam + @DefaultValue 的墙。

【问题讨论】:

  • 如果您使用的是 JAX-RS 2.0,请参阅 @BeanParam
  • @peeskillet 这看起来确实很有趣。我不认为可以注入构造函数或使用任何钩子。我真的很想拥有一个 Optional&lt;UUID&gt; 成员字段,而不是可以为空的 UUID
  • 你可以将@Context UriInfo注入到bean类中。不要注释Optional 字段,在getter 中,通过UriInfo 获取它。我不太确定你需要在构造函数中注入什么或者你在说什么钩子
  • 唯一的其他选择是为Optional 创建一个ParamConverter,如果您经常使用Optional,这可能是您想要做的事情。见example
  • 虽然您已经找到了解决方案,但我建议您不要将用户名、密码或应用密钥等身份验证参数作为查询参数包含在内,原因有几个,例如缓存或复制和粘贴问题。它们实际上应该是请求的身份验证标头的一部分。因此,这里server-filter 可能有用。

标签: java jax-rs


【解决方案1】:

如果您使用 JAX-RS 2.0,则可以使用 @BeanParam,它允许您将任意 @XxxParam 注释属性和 @Context 对象注入任意 bean 类。例如

public class Bean {
    @QueryParam("blah")
    String blah;
}

@GET
public Response get(@BeanParam Bean bean) {}

如果你想要不可变,你甚至可以注入到构造函数中。例如

public static class Pagination {

    private final List<String> sort;
    private final Optional<String> from;

    public Pagination(@QueryParam("sort") List<String> sort, 
                      @QueryParam("from") Optional<String> from) {
        this.sort = sort;
        this.from = from;
    }

    public List<String> getSort() { return sort; }
    public Optional<String> getFrom() { return from; }
}

如果您注意到Optional 正在被注入。通常这是不可能的,但我为它创建了一个ParamConverter。您可以在this answer 中阅读有关它的更多信息。它基本上允许我们注入任意对象,从参数的 String 值创建。

@Provider
public static class OptionalParamProvider implements ParamConverterProvider {

    @Override
    public <T> ParamConverter<T> getConverter(Class<T> rawType, 
                                              Type genericType, 
                                              Annotation[] annotations) {
        if (Optional.class != rawType) {
            return null;
        }

        return (ParamConverter<T>)new ParamConverter<Optional>() {

            @Override
            public Optional fromString(String value) {
                return Optional.ofNullable(value);
            }

            @Override
            public String toString(Optional value) {
                return value.toString();
            }  
        };
    }  
}

OptionalParamProvider 的好处是它允许您在需要注入@FormParam@QueryParam@PathParm 和所有其他@XxxParams 的任何地方使用Optional(multitpart 除外) .

我不知道您使用的是什么 JAX-RS 实现,但以上内容应该适用于所有实现。下面是一个 Jersey 测试用例,使用 Jersey Test Framework。您可以像运行任何其他 JUnit 测试一样运行该类。

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Optional;
import javax.ws.rs.BeanParam;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ParamConverter;
import javax.ws.rs.ext.ParamConverterProvider;
import javax.ws.rs.ext.Provider;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import org.junit.Test;

public class BeanParamTest extends JerseyTest {

    @Provider
    public static class OptionalParamProvider implements ParamConverterProvider {

        @Override
        public <T> ParamConverter<T> getConverter(Class<T> rawType, 
                                                  Type genericType, 
                                                  Annotation[] annotations) {
            if (Optional.class != rawType) {
                return null;
            }

            return (ParamConverter<T>)new ParamConverter<Optional>() {

                @Override
                public Optional fromString(String value) {
                    return Optional.ofNullable(value);
                }

                @Override
                public String toString(Optional value) {
                    return value.toString();
                }  
            };
        }  
    }

    public static class Pagination {

        private final List<String> sort;
        private final Optional<String> from;

        public Pagination(@QueryParam("sort") List<String> sort, 
                          @QueryParam("from") Optional<String> from) {
            this.sort = sort;
            this.from = from;
        }

        public List<String> getSort() { return sort; }
        public Optional<String> getFrom() { return from; }
    }

    @Path("bean")
    public static class PaginationResource {

        @GET
        public String get(@BeanParam Pagination pagination) {
            StringBuilder sb = new StringBuilder();
            sb.append(pagination.getSort().toString());
            if (pagination.getFrom().isPresent()) {
                sb.append(pagination.getFrom().get());
            }
            return sb.toString();
        }
    }

    @Override
    public ResourceConfig configure() {
        return new ResourceConfig(PaginationResource.class)
                .register(OptionalParamProvider.class);
    }

    @Test
    public void should_return_all_sort_and_from() {
        Response response = target("bean")
                .queryParam("sort", "foo")
                .queryParam("sort", "bar")
                .queryParam("from", "baz")
                .request().get();
        assertEquals(200, response.getStatus());
        String message = response.readEntity(String.class);
        assertThat(message, containsString("foo"));
        assertThat(message, containsString("bar"));
        assertThat(message, containsString("baz"));
        System.out.println(message);
        response.close();
    }
}

这是运行测试所需的唯一 Maven 依赖项

<dependency>
    <groupId>org.glassfish.jersey.test-framework.providers</groupId>
    <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
    <version>2.19</version>
    <scope>test</scope>
</dependency>

【讨论】:

  • 一切都很好,除了构造函数注入:我不能让它在资源类之外工作(为了资源之间的可重用性)。我暂时保留注入器方法。
猜你喜欢
  • 2011-02-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多