如何优化Jackson动态过滤DTO字段方案?解决重复调用服务问题
动态字段过滤优化方案
需求背景
需根据动态变化的可用字段列表生成API响应,核心目标是通过Jackson序列化DTO时自动排除不在列表中的字段。示例如下:
示例DTO
Sample DTO { firstName: <String>, lastName: <String>, address: <String>, email: <String>, }
不同时刻的响应示例
- 可用字段1(时刻1):["firstName", "lastName", "address"]
Sample DTO final response { firstName: <String>, lastName: <String>, address: <String> } - 可用字段2(时刻2):["firstName", "email"]
Sample DTO final response { firstName: <String>, email: <String>, }
现有实现及问题
当前已通过Jackson的@JsonFilter实现功能,但存在**序列化每个字段时都会调用一次mockService.getAvailableFields()**的问题,导致不必要的重复调用,影响性能。
现有代码实现
SampleDTO
@AllArgsConstructor @NoArgsConstructor @Getter @Setter @JsonFilter("myFilter") public class SampleDTO { String firstName; String lastName; String address; String email; }
CustomObjectMapper
public class CustomObjectMapper { // 替换Quarkus内置的ObjectMapper的CDI生产者 @Singleton @Produces ObjectMapper objectMapper(Instance<ObjectMapperCustomizer> customizers) { ObjectMapper mapper = new ObjectMapper(); // 自定义ObjectMapper // 应用所有ObjectMapperCustomizer bean(包括Quarkus自带的) for (ObjectMapperCustomizer customizer : customizers) { customizer.customize(mapper); } return mapper; } }
RegisterCustomModuleCustomizer
@Singleton public class RegisterCustomModuleCustomizer implements ObjectMapperCustomizer { @Inject MockService mockService; public void customize(ObjectMapper mapper) { FilterProvider filters = new SimpleFilterProvider().addFilter("myFilter", new PropertyFilter(mockService)); mapper.setFilterProvider(filters); } }
PropertyFilter
@AllArgsConstructor public class PropertyFilter extends SimpleBeanPropertyFilter { MockService mockService; @Override public void serializeAsField(Object pojo, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer) throws Exception { if (include(writer)) { List<String> availableFields = mockService.getAvailableFields(); if (availableFields.contains(writer.getName())) { writer.serializeAsField(pojo, jgen, provider); } } else if (!jgen.canOmitFields()) { // since 2.3 writer.serializeAsOmittedField(pojo, jgen, provider); } } @Override protected boolean include(BeanPropertyWriter writer) { return true; } @Override protected boolean include(PropertyWriter writer) { return true; } }
MockService
@ApplicationScoped public class MockService { public List<String> getAvailableFields() { return Arrays.asList("firstName", "lastName", "address"); } }
优化方案
方案一:缓存序列化对象的可用字段
修改自定义PropertyFilter,利用SerializerProvider的属性缓存当前对象的可用字段,确保每个对象序列化过程中仅调用一次mockService.getAvailableFields():
@AllArgsConstructor public class PropertyFilter extends SimpleBeanPropertyFilter { private final MockService mockService; @Override public void serializeAsField(Object pojo, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer) throws Exception { // 尝试从SerializerProvider获取缓存的可用字段 List<String> availableFields = (List<String>) provider.getAttribute("availableFields"); // 第一次处理该对象时,获取并缓存字段列表 if (availableFields == null) { availableFields = mockService.getAvailableFields(); provider.setAttribute("availableFields", availableFields); } if (include(writer) && availableFields.contains(writer.getName())) { writer.serializeAsField(pojo, jgen, provider); } else if (!jgen.canOmitFields()) { writer.serializeAsOmittedField(pojo, jgen, provider); } } @Override protected boolean include(BeanPropertyWriter writer) { return true; } @Override protected boolean include(PropertyWriter writer) { return true; } }
原理:SerializerProvider在每个序列化请求中是独立实例,将可用字段存入其属性后,同一对象的所有字段处理时均可复用,避免重复调用服务方法。
方案二:请求级动态设置Filter(更简洁)
直接在请求处理逻辑中获取可用字段,使用Jackson自带的filterOutAllExcept方法创建Filter,无需自定义PropertyFilter,每个请求仅调用一次服务方法:
修改请求处理代码
@Inject MockService mockService; @Inject ObjectMapper objectMapper; @GET @Produces(MediaType.APPLICATION_JSON) public Response getSample() { SampleDTO dto = new SampleDTO("John", "Doe", "123 St", "john@example.com"); // 仅调用一次获取可用字段 List<String> availableFields = mockService.getAvailableFields(); // 创建临时FilterProvider,只保留指定字段 FilterProvider filterProvider = new SimpleFilterProvider() .addFilter("myFilter", SimpleBeanPropertyFilter.filterOutAllExcept(availableFields)); // 使用该FilterProvider序列化对象 String json = objectMapper.writer(filterProvider).writeValueAsString(dto); return Response.ok(json).build(); }
简化代码结构
同时可删除自定义的PropertyFilter类,减少代码复杂度。
优势:逻辑直观,避免自定义Filter的复杂逻辑,完全杜绝重复调用服务方法的问题。
内容的提问来源于stack exchange,提问作者Dmirii Berrington
相关产品推荐
相关产品推荐

