You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何优化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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 00:45:13