Spring控制器中使用多态DTO时如何避免instanceof判断?
避免Spring控制器中多态DTO的instanceof判断方案
问题背景
我通过Jackson实现了多态DTO结构,代码如下:
@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION, defaultImpl = HomeAddressDto.class) @JsonSubTypes({ @Type(HomeAddressDto.class), @Type(OfficeAddressDto.class) }) public interface Address { } public record HomeAddressDto(String street, String postalCode, String unit) implements Address {} public record OfficeAddressDto(String building, String street, String postalCode, String unit) implements Address {}
这段序列化逻辑运行正常,但在Spring控制器中,必须通过instanceof判断具体的DTO类型才能执行对应逻辑:
@RestController @RequestMapping("/deduction/addresses") public class DeductionAddressController { @PostMapping() public Address create(@RequestBody Address address) { if (address instanceof HomeAddressDto) { System.out.println("home"); } if (address instanceof OfficeAddressDto) { System.out.println("office"); } return address; } }
以下是两种可行的优化方案,避免instanceof的硬编码判断:
方案一:让DTO承担自身行为(推荐简单场景)
修改Address接口,定义抽象的处理方法,让各个DTO子类实现自身的业务逻辑:
- 更新
Address接口:
public interface Address { void process(); }
- 让两个Record实现
process方法:
public record HomeAddressDto(String street, String postalCode, String unit) implements Address { @Override public void process() { System.out.println("home"); // 这里可以写入Home地址对应的业务逻辑 } } public record OfficeAddressDto(String building, String street, String postalCode, String unit) implements Address { @Override public void process() { System.out.println("office"); // 这里可以写入Office地址对应的业务逻辑 } }
- 简化控制器代码:
@RestController @RequestMapping("/deduction/addresses") public class DeductionAddressController { @PostMapping() public Address create(@RequestBody Address address) { address.process(); // 直接调用,无需判断类型 return address; } }
这种方式符合开闭原则,后续新增Address子类时,只需要实现process方法即可,无需修改控制器代码。
方案二:访问者模式(适合复杂业务逻辑分离场景)
如果业务逻辑不适合放在DTO中(比如逻辑依赖其他服务、代码量较大),可以用访问者模式分离逻辑:
- 定义访问者接口:
public interface AddressVisitor { void visit(HomeAddressDto homeAddress); void visit(OfficeAddressDto officeAddress); }
- 更新
Address接口,添加接受访问者的方法:
public interface Address { void accept(AddressVisitor visitor); }
- 让DTO子类实现
accept方法:
public record HomeAddressDto(String street, String postalCode, String unit) implements Address { @Override public void accept(AddressVisitor visitor) { visitor.visit(this); } } public record OfficeAddressDto(String building, String street, String postalCode, String unit) implements Address { @Override public void accept(AddressVisitor visitor) { visitor.visit(this); } }
- 控制器中使用访问者处理逻辑:
@RestController @RequestMapping("/deduction/addresses") public class DeductionAddressController { // 可以将访问者封装成单独的Bean注入,避免控制器代码臃肿 @PostMapping() public Address create(@RequestBody Address address) { address.accept(new AddressVisitor() { @Override public void visit(HomeAddressDto homeAddress) { System.out.println("home"); // 这里可以注入其他服务,处理复杂业务逻辑 } @Override public void visit(OfficeAddressDto officeAddress) { System.out.println("office"); // 处理Office地址的复杂业务逻辑 } }); return address; } }
这种方式将业务逻辑与DTO解耦,适合逻辑复杂、需要依赖外部服务的场景。
内容的提问来源于stack exchange,提问作者robert trudel
相关产品推荐
相关产品推荐

