@ControllerAdvice 如何捕获降级触发异常
使用 @restcontrolleradvice 配合全局异常处理器可以统一捕获服务降级时抛出的异常。核心做法是定义一个继承自 runtimeexception 的自定义降级异常类,并在控制器增强器中针对该特定类型编写 @exceptionhandler 方法,从而拦截并返回标准化的错误响应。

在 Spring Boot 应用中,当调用外部依赖(如 Feign Client)失败并触发熔断降级逻辑时,通常会抛出特定的运行时异常。通过在全局异常处理层注册对该异常类型的监听,应用可以在异常到达视图解析器之前将其转换为 JSON 格式的错误信息。这种方法避免了在每个 Controller 方法中重复编写 try-catch 块,实现了关注点分离。
package com.example.demo.exception;
public class ServiceDegradedException extends RuntimeException {
public ServiceDegradedException(String message) {
super(message);
}
}package com.example.demo.controller;
import org.springframework.web.bind.annotation.*;
import com.example.demo.exception.ServiceDegradedException;
@RestController
@RequestMapping("/api")
public class DemoController {
@GetMapping("/fallback")
public String triggerFallback() {
// 模拟触发降级逻辑
throw new ServiceDegradedException("Service unavailable, fallback triggered");
}
}package com.example.demo.config;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import com.example.demo.exception.ServiceDegradatedException;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ServiceDegradatedException.class)
public ResponseEntity<String> handleDegradation(ServiceDegradatedException ex) {
return ResponseEntity
.status(HttpStatus.SERVICE_UNAVAILABLE)
.body("Degraded: " + ex.getMessage());
}
}上述代码定义了三个关键部分:自定义异常类、触发异常的 Controller 以及处理异常的 Advice。当访问 /api/fallback 接口时,Spring MVC 会捕获抛出的 ServiceDegradatedException,并将其映射到 GlobalExceptionHandler 中的处理方法,最终返回 HTTP 503 状态码和自定义消息。
Feign Client 降级回调中的异常传递机制
Feign Client 在启用 Hystrix 或 Resilience4j 进行降级时,若降级方法内部再次抛出异常,该异常会被透传至主线程,进而被全局异常处理器捕获。关键在于确保降级方法签名与原始方法一致,且抛出的异常类型属于全局处理器已注册的范畴。

在实际开发中,Feign Client 的降级实现类通常实现了一个 fallback 接口。如果在这个 fallback 实现中直接抛出业务异常,Spring Boot 的全局异常处理机制依然有效。这是因为 Feign 的客户端拦截器在检测到降级后,会将控制权交还给调用方,此时抛出的异常被视为普通 Java 异常,进入标准的异常传播链。
package com.example.demo.client;
import com.example.demo.exception.ServiceDegradatedException;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
@FeignClient(name = "service-provider", fallback = ServiceProviderFallback.class)
public interface ServiceProviderClient {
@GetMapping("/data")
String getData();
}
@Component
class ServiceProviderFallback implements ServiceProviderClient {
@Override
public String getData() {
// 降级逻辑中主动抛出异常
throw new ServiceDegradatedException("Fallback executed due to timeout");
}
}这段代码展示了 Feign Client 的定义及其对应的 Fallback 类。当 service-provider 不可用时,getData() 方法将执行 ServiceProviderFallback 中的逻辑。由于 getData() 的返回值类型为 String,但我们在降级方法中抛出了异常,这会导致调用者收到一个未处理的异常。此时,前文定义的 GlobalExceptionHandler 将介入处理,确保前端接收到友好的错误提示而非堆栈跟踪。
全局异常处理对降级返回值的覆盖行为
当降级方法既返回了具体数据又触发了全局异常处理时,需明确异常优先原则。若降级方法内部抛出异常,全局异常处理器将完全接管响应流程,忽略降级方法原本可能返回的任何非空值。只有当降级方法正常返回而不抛出异常时,全局异常处理器才会跳过,由 Spring MVC 直接序列化返回值。

理解这一行为对于设计健壮的 API 至关重要。开发者应权衡是在降级方法中返回默认静态数据,还是抛出异常交由全局处理器统一格式化。选择抛出异常的方式可以获得更一致的错误码和结构,而选择返回默认数据则可能掩盖潜在的服务故障信号。建议根据业务场景决定:对于可恢复的临时故障,返回默认数据;对于严重不可用,抛出异常以触发告警或重试机制。
package com.example.demo.client;
import com.example.demo.exception.ServiceDegradatedException;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
@FeignClient(name = "service-provider", fallback = ServiceProviderFallback2.class)
public interface ServiceProviderClient2 {
@GetMapping("/info")
String getInfo();
}
@Component
class ServiceProviderFallback2 implements ServiceProviderClient2 {
@Override
public String getInfo() {
// 方式一:返回默认值,不触发全局异常处理
return "{\"status\":\"degraded\",\"data\":null}";
}
}
@Component
class ServiceProviderFallback3 implements ServiceProviderClient2 {
@Override
public String getInfo() {
// 方式二:抛出异常,触发全局异常处理
throw new ServiceDegradatedException("Critical failure in fallback");
}
}对比 ServiceProviderFallback2 和 ServiceProviderFallback3 可以看出,前者静默返回 JSON 字符串,后者则引发异常。在全局异常处理器存在的情况下,后者会被 @ExceptionHandler 捕获并转换为 HTTP 503 响应,而前者则直接以 HTTP 200 状态码返回内容。这种差异直接影响前端对请求状态的判断,因此必须在设计阶段明确约定降级的语义。


















