Spring Boot 优雅处理多态 JSON 入参与策略模式结合实践
在 Spring Boot 开发中,如何优雅地处理多态 JSON 入参并结合策略模式,根据 type
动态调用对应的逻辑,是一个非常实用的技术点。本文将详细介绍如何实现这一功能,并通过结合策略模式动态选择组件,使代码结构更加清晰、扩展性更强。
一、场景描述
假设我们有一个通用的 OCR 接口,接收不同类型的识别请求。请求体包含一个 type
字段,用于指明需要调用的具体识别逻辑,例如:
- ID 卡识别(
ID_CARD
) - 车辆识别(
VEHICLE
) - 营业执照识别(
BUSINESS_LICENSE
) - 车票识别(
CAR_INVOICE
)
请求体示例如下:
{
"type": "ID_CARD",
"idNumber": "123456789",
"name": "John Doe"
}
我们希望在控制器接收到请求后,能够根据 type
字段的值动态调用对应的识别组件,同时保持代码的高内聚和低耦合。
二、解决方案概述
实现上述功能的核心包括以下几个步骤:
- 设计多态 JSON 入参结构:使用
@JsonTypeInfo
和@JsonSubTypes
注解。 - 利用策略模式,根据
type
字段选择对应的逻辑组件。 - 通过 Spring 注入管理策略组件,实现松耦合的动态调用。
- 完善异常处理机制,提供用户友好的错误提示。
三、详细实现
3.1 定义通用父类和子类
首先,我们设计一个通用的父类 CommonOCRDTO
,并通过 Jackson 的多态注解支持根据 type
字段解析为不同的子类。
父类设计
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type"
)
@JsonSubTypes({
@JsonSubTypes.Type(value = IdCardRecognizerDTO.class, name = "ID_CARD"),
@JsonSubTypes.Type(value = VehicleOCRDTO.class, name = "VEHICLE"),
@JsonSubTypes.Type(value = BusinessLicenseOCRDTO.class, name = "BUSINESS_LICENSE"),
@JsonSubTypes.Type(value = CarInvoiceOCRDTO.class, name = "CAR_INVOICE")
})
public abstract class CommonOCRDTO {
private String type;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
子类设计
每个子类对应具体的 OCR 请求参数。例如:
public class IdCardRecognizerDTO extends CommonOCRDTO {
private String idNumber;
private String name;
// Getters and Setters
}
public class VehicleOCRDTO extends CommonOCRDTO {
private String vehicleNumber;
private String owner;
// Getters and Setters
}
3.2 定义 OCR 策略接口和实现类
策略接口
public interface OCRStrategy<T extends CommonOCRDTO, R> {
String getType(); // 返回策略对应的 type 值
R process(T request); // 处理请求
}
策略实现类
根据不同的 OCR 类型,实现对应的逻辑。例如:
@Service
public class IdCardOCRStrategy implements OCRStrategy<IdCardRecognizerDTO, String> {
@Override
public String getType() {
return "ID_CARD";
}
@Override
public String process(IdCardRecognizerDTO request) {
return "Processing ID Card for: " + request.getName();
}
}
@Service
public class VehicleOCRStrategy implements OCRStrategy<VehicleOCRDTO, String> {
@Override
public String getType() {
return "VEHICLE";
}
@Override
public String process(VehicleOCRDTO request) {
return "Processing Vehicle for: " + request.getVehicleNumber();
}
}
3.3 实现策略选择器
利用 Spring 的 @Component
和依赖注入机制管理所有策略类,通过 type
动态选择策略。
@Component
public class OCRStrategyContext {
private final Map<String, OCRStrategy<?, ?>> strategyMap = new HashMap<>();
@Autowired
public OCRStrategyContext(List<OCRStrategy<?, ?>> strategies) {
for (OCRStrategy<?, ?> strategy : strategies) {
strategyMap.put(strategy.getType(), strategy);
}
}
@SuppressWarnings("unchecked")
public <T extends CommonOCRDTO, R> OCRStrategy<T, R> getStrategy(String type) {
OCRStrategy<?, ?> strategy = strategyMap.get(type);
if (strategy == null) {
throw new IllegalArgumentException("No strategy found for type: " + type);
}
return (OCRStrategy<T, R>) strategy;
}
}
3.4 Controller 中调用策略
在 Controller 中,根据 type
字段动态选择对应的策略并调用处理逻辑:
@RestController
@RequestMapping("/ocr")
public class OCRController {
private final OCRStrategyContext strategyContext;
@Autowired
public OCRController(OCRStrategyContext strategyContext) {
this.strategyContext = strategyContext;
}
@PostMapping("/recognize")
public ResponseEntity<Object> recognize(@RequestBody CommonOCRDTO request) {
OCRStrategy<CommonOCRDTO, Object> strategy = strategyContext.getStrategy(request.getType());
Object result = strategy.process(request);
return ResponseEntity.ok(result);
}
}
3.5 全局异常处理
为了提升用户体验,我们需要对无效的 type
值或其他异常提供友好的提示。
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException ex) {
return ResponseEntity.badRequest().body(ex.getMessage());
}
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<String> handleHttpMessageNotReadableException(HttpMessageNotReadableException ex) {
return ResponseEntity.badRequest().body("Invalid JSON format or unknown type.");
}
}
四、完整流程示例
4.1 请求示例
- 请求体:
{
"type": "ID_CARD",
"idNumber": "123456789",
"name": "John Doe"
}
- 响应示例:
{
"result": "Processing ID Card for: John Doe"
}
- 如果传入无效的
type
:
{
"type": "UNKNOWN_TYPE"
}
- 响应示例:
{
"error": "No strategy found for type: UNKNOWN_TYPE"
}
五、总结
通过结合 Jackson 多态反序列化 和 策略模式,我们实现了以下目标:
-
简化了多态 JSON 入参的解析:
- 使用
@JsonTypeInfo
和@JsonSubTypes
动态解析入参。
- 使用
-
动态选择业务逻辑组件:
- 使用策略模式封装每种 OCR 类型的处理逻辑。
- 利用 Spring 的依赖注入自动管理和选择策略。
-
增强了代码的可扩展性:
- 新增业务类型时,只需添加新的策略实现类和子类。
-
完善了异常处理:
- 针对无效的
type
或 JSON 格式错误提供友好的错误提示。
- 针对无效的
通过这种设计,我们实现了优雅的多态 JSON 入参处理和高效的动态业务逻辑调用,为类似场景提供了一种通用的解决方案。
评论区