@CircuitBreaker fallback方法在@Valid @RequestBody异常时不生效问题排查
问题:Spring API中JSON校验异常无法触发CircuitBreaker
我创建了一个小型Spring API项目,想要为JSON校验异常配置CircuitBreaker异常处理机制,但当请求体存在空字段抛出校验异常时,CircuitBreaker从未触发。推测异常是在方法执行前抛出的,但不知道怎么在这种场景下使用CircuitBreaker。
代码实现
对象模型
import javax.validation.constraints.NotNull; import lombok.Data; @Data public class Client { @NotNull private Long id; @NotNull private String name; @NotNull private Integer age; }
控制器POST方法
@RestController @RequestMapping("/client") public class ClientController { @PostMapping("/create") @CircuitBreaker(name="validationDataHandler", fallbackMethod = "fallbackCreateClient") public Mono<ResponseEntity<Client>> createClient(@Valid @RequestBody Client client) { System.out.println("------- METHOD CREATE -------"); return clientService.createClient(client) .map(created -> ResponseEntity.ok(created)); } private Mono<ResponseEntity<Client>> fallbackCreateClient(Client client, Throwable e) { System.out.println("<<<< CIRCUIT BREAKER >>>>"); return Mono.just(ResponseEntity.badRequest()); } }
依赖配置(pom.xml)
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.5</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.amigo.programador</groupId> <artifactId>ms-client</artifactId> <version>0.0.1-SNAPSHOT</version> <name>ms-client</name> <description>Project for Spring Boot</description> <properties> <java.version>11</java.version> <spring-cloud.version>2021.0.3</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>com.amigo.programador</groupId> <artifactId>library</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
问题原因
你猜的没错,@Valid注解的校验是在控制器方法执行前完成的——Spring的请求处理流程中,参数校验是在方法调用的前置阶段(由MethodValidationInterceptor处理),而@CircuitBreaker是通过AOP环绕方法执行的,只有方法内部抛出的异常才会被CircuitBreaker捕获。所以校验异常根本没进入到被CircuitBreaker包裹的方法逻辑里,自然不会触发熔断。
解决方法
方案1:手动在方法内触发校验
去掉控制器方法参数上的@Valid,改为在方法内部手动调用校验器,把校验逻辑放到CircuitBreaker的覆盖范围内:
- 注入
Validator到控制器:
@Autowired private Validator validator;
- 修改控制器方法:
@PostMapping("/create") @CircuitBreaker(name="validationDataHandler", fallbackMethod = "fallbackCreateClient") public Mono<ResponseEntity<Client>> createClient(@RequestBody Client client) { // 手动执行校验 Set<ConstraintViolation<Client>> violations = validator.validate(client); if (!violations.isEmpty()) { // 抛出校验异常,让CircuitBreaker捕获 throw new ConstraintViolationException(violations); } System.out.println("------- METHOD CREATE -------"); return clientService.createClient(client) .map(created -> ResponseEntity.ok(created)); }
这样校验异常会在方法内部抛出,CircuitBreaker就能捕获并触发熔断了。
方案2:全局异常处理器结合CircuitBreaker(推荐)
如果不想修改每个控制器方法的校验逻辑,可以通过全局异常处理器捕获校验异常,再在异常处理器里调用被CircuitBreaker包裹的处理方法:
- 创建一个处理校验异常的服务类,用
@CircuitBreaker标记处理方法:
@Service public class ValidationFallbackService { @CircuitBreaker(name="validationDataHandler", fallbackMethod = "validationFallback") public Mono<ResponseEntity<Void>> handleValidationException(ConstraintViolationException e) { // 这里可以添加自定义的校验异常处理逻辑,比如记录日志 throw e; // 抛出异常触发熔断 } public Mono<ResponseEntity<Void>> validationFallback(ConstraintViolationException e, Throwable throwable) { System.out.println("<<<< CIRCUIT BREAKER >>>>"); return Mono.just(ResponseEntity.badRequest().build()); } }
- 全局异常处理器中调用这个服务:
@RestControllerAdvice public class GlobalExceptionHandler { @Autowired private ValidationFallbackService validationFallbackService; @ExceptionHandler(ConstraintViolationException.class) public Mono<ResponseEntity<Void>> handleValidationError(ConstraintViolationException e) { return validationFallbackService.handleValidationException(e); } }
这种方式更符合全局处理的设计,同时能让CircuitBreaker捕获到校验异常并触发熔断。
补充说明
- 确保你的CircuitBreaker配置(比如失败阈值、熔断时间等)是正确的,默认配置可能需要调整才能快速触发熔断。
- 对于Reactive场景,Resilience4j的CircuitBreaker已经支持Mono/Flux,不需要额外配置就能正常工作。
内容的提问来源于stack exchange,提问作者Renzo EC
相关产品推荐
相关产品推荐

