Jersey稀疏查询:JAX-RS端点能否返回POJO的过滤视图?
当然可以!在JAX-RS(Jersey)里实现这种根据请求fields参数返回POJO过滤视图的功能,有几种常用且优雅的方式,下面给你详细拆解:
方法一:使用Jackson动态过滤(推荐)
Jackson本身支持动态字段过滤,配合Jersey的特性可以轻松实现需求,这也是最常用的方案:
准备Jackson依赖
确保项目中引入jackson-databind和jackson-jaxrs-base相关依赖(如果使用Maven/Gradle,直接添加对应坐标即可)。定义过滤注解与过滤器
先给需要动态过滤的实体类标记一个自定义注解,再写一个Jackson的PropertyFilter来处理字段筛选:// 自定义标记注解 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface DynamicFilter { } // 自定义字段过滤器 public class DynamicFieldFilter extends SimpleBeanPropertyFilter { private final Set<String> allowedFields; public DynamicFieldFilter(Set<String> allowedFields) { this.allowedFields = allowedFields; } @Override protected boolean include(BeanPropertyWriter writer) { // 如果没有指定字段,或者字段在允许列表中,就保留 return allowedFields == null || allowedFields.isEmpty() || allowedFields.contains(writer.getName()); } @Override protected boolean include(PropertyWriter writer) { return include((BeanPropertyWriter) writer); } }在端点中动态应用过滤
给Person类加上注解,然后在资源方法里解析fields参数,动态配置Jackson的序列化规则:@Path("/persons") public class PersonResource { @GET @Path("/{id}") @Produces(MediaType.APPLICATION_JSON) public Response getPerson(@PathParam("id") String id, @QueryParam("fields") String fields) { // 模拟从数据库/服务获取Person对象 Person person = new Person("Alice", 27); // 初始化Jackson ObjectMapper并配置过滤器 ObjectMapper mapper = new ObjectMapper(); FilterProvider filterProvider = new SimpleFilterProvider() .addFilter("dynamicFilter", new DynamicFieldFilter(parseFields(fields))); mapper.setFilterProvider(filterProvider); try { String filteredJson = mapper.writeValueAsString(person); return Response.ok(filteredJson).build(); } catch (JsonProcessingException e) { return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); } } // 解析fields参数为集合 private Set<String> parseFields(String fields) { if (fields == null || fields.trim().isEmpty()) { return null; } return new HashSet<>(Arrays.asList(fields.split(","))); } } // 给Person类添加过滤注解 @DynamicFilter @JsonFilter("dynamicFilter") public class Person { private String name; private Integer age; // 构造器、Getter/Setter省略 }这样请求
/persons/23?fields=name时会返回{"name":"Alice"},请求/persons/23?fields=age时返回{"age":27}。
方法二:使用Jersey原生Entity Filtering
Jersey自带了Entity Filtering特性,适合更复杂的权限/场景过滤,无需依赖Jackson的额外配置:
启用Entity Filtering特性
先引入jersey-entity-filtering依赖,然后在Jersey应用配置类中注册特性:public class MyJerseyApplication extends ResourceConfig { public MyJerseyApplication() { packages("your.resource.package"); register(EntityFilteringFeature.class); } }编程式指定返回字段
在资源方法中,根据fields参数创建EntityGraph,指定要保留的字段:@GET @Path("/{id}") @Produces(MediaType.APPLICATION_JSON) public Response getPerson(@PathParam("id") String id, @QueryParam("fields") String fields) { Person person = new Person("Bob", 30); EntityGraph<Person> entityGraph = EntityGraphs.createEntityGraph(Person.class); if (fields != null && !fields.isEmpty()) { // 拆分fields参数并添加到实体图 Arrays.stream(fields.split(",")) .map(String::trim) .forEach(field -> entityGraph.addAttributeNodes(field)); } else { // 无fields参数时返回所有字段 entityGraph.addAttributeNodes("name", "age"); } return Response.ok() .entity(person, entityGraph) .build(); }这种方式不需要修改
Person类的注解,灵活性更高。
方法三:自定义MessageBodyWriter(高度定制场景)
如果上面两种方案都满足不了你的特殊需求,可以自定义MessageBodyWriter手动控制序列化逻辑:
@Provider @Produces(MediaType.APPLICATION_JSON) public class DynamicPersonWriter implements MessageBodyWriter<Person> { @Override public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { return Person.class.isAssignableFrom(type); } @Override public void writeTo(Person person, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException { // 从请求上下文获取fields参数 ContainerRequestContext requestContext = ((ContainerRequest) RequestContextProvider.getRequest()).getContainerRequest(); String fields = requestContext.getUriInfo().getQueryParameters().getFirst("fields"); ObjectNode jsonNode = JsonNodeFactory.instance.objectNode(); if (fields == null || fields.isEmpty()) { jsonNode.put("name", person.getName()); jsonNode.put("age", person.getAge()); } else { for (String field : fields.split(",")) { String trimmedField = field.trim(); switch (trimmedField) { case "name": jsonNode.put("name", person.getName()); break; case "age": jsonNode.put("age", person.getAge()); break; // 可扩展更多字段的处理逻辑 } } } entityStream.write(jsonNode.toString().getBytes()); } @Override public long getSize(Person person, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { return -1; // 让Jersey自动计算响应长度 } }
将这个Writer注册到Jersey应用后,就会自动处理Person对象的序列化。
内容的提问来源于stack exchange,提问作者More Than Five

