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

Java Spring中如何为各REST端点定制DTO且避免大量DTO类?

Java Spring REST接口的DTO优化方案探讨

背景

我基于Java & Spring开发了大量REST端点,示例如下:

  • GET /cars? => 获取所有车辆
  • GET /cars/<id> => 获取单辆车辆
  • PATCH /car/<id> => 更新单辆车辆
  • PATCH /cars-bulk => 批量更新车辆
  • POST /cars => 创建单辆车辆
  • POST /cars-bulk => 批量创建车辆

现有CarDTO类:

public class CarDTO {
  private String id;
  private String make;
  private String model;
  private String owner;
  private boolean isAvailable;
}

以及CarService接口定义:

public interface CarService {
  CarDTO getCarById(String id);
  List<CarDTO> getCars(CarDTO filter);
  CarDTO updateCar(CarDTO car);
  CarDTO createCar(CarDTO car);
  List<BulkUpdateResult> updateCars(List<CarDTO> car);
  List<BulkUpdateResult> createCars(List<CarDTO> car);
}

当前问题

当前DTO在各服务方法间共享,导致接口调用者不清楚每个方法需设置哪些字段:

  • getCarById和getCars返回除isAvailable外的所有字段
  • updateCar/updateCars仅允许更新owner字段
  • createCar/createCars可设置除id(由服务生成)外的所有字段

虽然可以用校验器和API文档解决,但我希望调用签名自解释,不包含无需生产或消费的字段。

尝试过的方案

  1. 独立DTO类:为每个端点创建独立DTO(如CarGetDTO、CarUpdateDTO、CarCreateDTO),但在大型代码库中会导致复杂度提升、代码重复。
  2. 多态继承:通过抽象基类扩展,但会增加代码复杂度,降低可读性,还存在字段重复问题:
public abstract class CarDTO {
  private String owner;
}

public class CarCreateDTO extends CarDTO  {
  private String make;
  private String model;
}

public class CarGetDTO extends CarCreateDTO  {
  private String id; // 重复代码
}

public class CarUpdateDTO extends CarCreateDTO  {
  private String id; // 重复代码
  private boolean isAvailable;
}

提问

是否存在更优、可扩展的定制DTO模式,无需逐个定义参数和返回类型?比如通过代码生成实现?


可行的优化方案

1. 基于注解的字段分组 + 代码生成(推荐)

用字段分组注解标记不同场景下的可用字段,配合代码生成工具自动生成对应场景的DTO,既保证调用签名自解释,又避免重复代码。

步骤:

  • 定义场景注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Create {}

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

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Get {}
  • 在基础CarDTO上标记字段所属场景:
public class CarDTO {
  @Get
  private String id;
  
  @Create @Get
  private String make;
  
  @Create @Get
  private String model;
  
  @Create @Update @Get
  private String owner;
  
  // 仅内部使用,不对外暴露
  private boolean isAvailable;
}
  • 用注解处理器(可基于MapStruct扩展或自定义)自动生成场景DTO:
    • CarCreateDTO包含make、model、owner字段
    • CarUpdateDTO仅包含owner字段
    • CarGetDTO包含id、make、model、owner字段
  • 服务层直接使用生成的DTO:
public interface CarService {
  CarGetDTO getCarById(String id);
  List<CarGetDTO> getCars(CarGetDTO filter);
  CarGetDTO updateCar(CarUpdateDTO car);
  CarGetDTO createCar(CarCreateDTO car);
  List<BulkUpdateResult> updateCars(List<CarUpdateDTO> car);
  List<BulkUpdateResult> createCars(List<CarCreateDTO> car);
}

这种方式只需维护基础DTO和注解,生成代码自动同步字段变化,彻底避免重复。

2. 使用@JsonView(快速解决,适合小型项目)

无需额外代码生成,用Spring的@JsonView控制序列化/反序列化字段,配合静态工厂方法强化调用签名的自解释性:

  • 定义视图类:
public class Views {
  public interface Create {}
  public interface Update {}
  public interface Get extends Create {}
}
  • 在CarDTO上标记字段对应视图:
public class CarDTO {
  @JsonView(Views.Get.class)
  private String id;
  
  @JsonView(Views.Create.class)
  private String make;
  
  @JsonView(Views.Create.class)
  private String model;
  
  @JsonView({Views.Create.class, Views.Update.class, Views.Get.class})
  private String owner;
  
  private boolean isAvailable;
}
  • 服务层指定视图,并提供静态工厂方法:
public interface CarService {
  @JsonView(Views.Get.class)
  CarDTO getCarById(String id);
  
  @JsonView(Views.Get.class)
  List<CarDTO> getCars(@JsonView(Views.Get.class) CarDTO filter);
  
  @JsonView(Views.Get.class)
  CarDTO updateCar(@JsonView(Views.Update.class) CarDTO car);
  
  @JsonView(Views.Get.class)
  CarDTO createCar(@JsonView(Views.Create.class) CarDTO car);
}

为场景添加静态构造方法,让调用方明确字段要求:

public class CarDTO {
  // ... 原有字段
  
  public static CarDTO forCreate(String make, String model, String owner) {
    CarDTO dto = new CarDTO();
    dto.make = make;
    dto.model = model;
    dto.owner = owner;
    return dto;
  }
  
  public static CarDTO forUpdate(String owner) {
    CarDTO dto = new CarDTO();
    dto.owner = owner;
    return dto;
  }
}

这种方案无需额外代码,适合小型项目,但自解释性略逊于独立DTO。

3. Java Record + 映射工具(简洁方案)

利用Java 16+的Record定义轻量不可变DTO,配合MapStruct自动完成与基础DTO的映射,代码量少且调用签名清晰:

  • 定义场景Record:
public record CarCreateDTO(String make, String model, String owner) {}
public record CarUpdateDTO(String owner) {}
public record CarGetDTO(String id, String make, String model, String owner) {}
  • 配置MapStruct映射器:
@Mapper(componentModel = "spring")
public interface CarMapper {
  CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
  
  CarDTO toCarDTO(CarCreateDTO createDTO);
  CarDTO toCarDTO(CarUpdateDTO updateDTO);
  CarGetDTO toCarGetDTO(CarDTO carDTO);
}
  • 服务层使用Record作为参数/返回值:
public interface CarService {
  CarGetDTO getCarById(String id);
  List<CarGetDTO> getCars(CarGetDTO filter);
  CarGetDTO updateCar(String id, CarUpdateDTO car);
  CarGetDTO createCar(CarCreateDTO car);
  // 批量方法同理
}

Record简洁不可变,MapStruct编译时校验映射正确性,字段变化时容易维护,唯一缺点是需手动同步Record字段。


内容的提问来源于stack exchange,提问作者mitchkman

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 19:35:35