一、BlockExceptionHandler类核心定位
BlockExceptionHandler是Spring Cloud Gateway中的核心异常处理类,专门用于处理网关路由过程中出现的阻塞式异常(BlockException)。该类属于网关的"异常熔断"组件,核心职责是统一捕获、处理因限流、熔断、降级等规则触发的异常,避免异常穿透到业务层,同时提供标准化的异常响应,保障网关服务的稳定性和用户体验。
需要注意的是,BlockExceptionHandler仅处理网关层面的"阻塞类异常",不负责处理业务服务抛出的异常(如接口报错、数据库异常等),业务异常需通过全局异常处理器(如@ControllerAdvice)单独处理。
二、核心应用场景
BlockExceptionHandler的应用场景与Spring Cloud Gateway的流量控制能力强绑定,主要集中在以下3类场景:
1. 限流触发异常处理
当网关配置了限流规则(如基于令牌桶、漏桶、计数器的限流),请求流量超过阈值时,会触发FlowException(限流异常),此时BlockExceptionHandler会捕获该异常,返回自定义响应(如"请求过于频繁,请稍后重试"),而非默认的500错误。
2. 熔断/降级触发异常处理
当网关集成Sentinel、Resilience4j等熔断组件,后端服务出现异常(如超时、报错率过高)触发熔断或降级时,会抛出DegradeException(降级异常),BlockExceptionHandler可统一拦截该异常,返回友好提示(如"服务暂时不可用,请稍后再试"),同时避免网关因后端服务异常而崩溃。
3. 授权拦截异常处理
部分场景下,网关会基于自定义规则拦截非法请求(如无权限、Token失效),并抛出AuthorityException等自定义BlockException子类,BlockExceptionHandler可统一处理这类授权异常,返回标准化的权限提示(如"无访问权限,请登录后重试"),保证权限拦截响应的一致性。
三、核心方法与实现规范
1. 核心方法
BlockExceptionHandler是一个函数式接口(Spring Cloud Gateway 2.0+),核心方法为:
java
Mono<Void> handle(ServerWebExchange exchange, Throwable throwable, BlockException ex);
-
ServerWebExchange:网关请求上下文,包含请求、响应对象,可通过该对象修改响应内容、状态码等。
-
Throwable:原始异常对象,可用于异常根源排查。
-
BlockException:阻塞式异常核心对象,包含异常类型(限流、降级、授权等)、异常信息等。
-
返回值Mono<Void>:符合WebFlux响应式编程模型,标识异常处理完成。
2. 实现规范
自定义BlockExceptionHandler需遵循以下规范:
-
实现BlockExceptionHandler接口,重写handle方法。
-
通过@Configuration注解将其注册为Spring Bean,覆盖默认的异常处理器。
-
针对不同类型的BlockException子类(FlowException、DegradeException等),做差异化处理。
-
响应结果需符合WebFlux规范,通过ServerHttpResponse写入响应体。
四、实战示例
以下示例基于Spring Cloud Gateway + Sentinel,实现自定义BlockExceptionHandler,处理限流、降级异常,并返回JSON格式响应。
1. 引入依赖
XML
<!-- Spring Cloud Gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Sentinel 适配 Gateway -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
<version>1.8.6</version>
</dependency>
2. 自定义BlockExceptionHandler实现类
java
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.BlockException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.cloud.gateway.filter.factory.SentinelGatewayFilterFactory.BlockExceptionHandler;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class CustomBlockExceptionHandler implements BlockExceptionHandler {
// JSON序列化工具
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable throwable, BlockException ex) {
ServerHttpResponse response = exchange.getResponse();
// 设置响应格式为JSON
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS); // 默认状态码,可根据异常类型调整
// 构建响应结果
Map<String, Object> result = new HashMap<>();
result.put("success", false);
// 区分异常类型,返回不同提示
if (ex instanceof FlowException) {
result.put("code", 1001);
result.put("message", "请求过于频繁,已触发限流保护");
response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
} else if (ex instanceof DegradeException) {
result.put("code", 1002);
result.put("message", "服务暂时不可用,已触发降级保护");
response.setStatusCode(HttpStatus.SERVICE_UNAVAILABLE);
} else {
result.put("code", 1003);
result.put("message", "请求被拦截,暂无访问权限");
response.setStatusCode(HttpStatus.FORBIDDEN);
}
// 将响应结果写入响应体
try {
byte[] bytes = objectMapper.writeValueAsBytes(result);
DataBuffer buffer = response.bufferFactory().wrap(bytes);
return response.writeWith(Mono.just(buffer));
} catch (JsonProcessingException e) {
// 序列化失败时返回默认提示
String defaultMsg = "{\"success\":false,\"code\":500,\"message\":\"服务器异常\"}";
DataBuffer buffer = response.bufferFactory().wrap(defaultMsg.getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(buffer));
}
}
}
3. 配置说明与效果验证
(1)配置限流规则(以Sentinel为例)
在application.yml中配置网关路由及限流规则:
java
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/user/**filters:
- name: SentinelGatewayFilter
args:
resourceName: user-service-route # 资源名称,用于限流规则配置
sentinel:
transport:
dashboard: localhost:8080 # Sentinel控制台地址,用于配置限流规则
(2)效果验证
-
当请求频率超过Sentinel配置的限流阈值时,网关会返回JSON响应:
{"success":false,"code":1001,"message":"请求过于频繁,已触发限流保护"},状态码429。 -
当后端user-service服务触发熔断时,返回响应:
{"success":false,"code":1002,"message":"服务暂时不可用,已触发降级保护"},状态码503。
五、注意事项
-
异常类型区分:BlockException有多个子类,需精准判断异常类型,避免所有异常返回同一提示,影响问题排查。
-
响应式编程适配:网关基于WebFlux,处理响应时需使用Mono、DataBuffer等响应式组件,不可使用传统的Servlet API(如HttpServletResponse)。
-
性能优化:异常处理逻辑需简洁,避免在handle方法中执行耗时操作(如数据库查询),防止影响网关吞吐量。
-
日志记录:建议在异常处理中记录日志(如异常类型、请求路径、时间戳),便于后续问题排查和流量分析。
六、扩展场景
-
多环境适配:可通过配置文件区分开发、测试、生产环境,返回不同详细程度的异常信息(生产环境隐藏敏感信息,开发环境显示完整异常栈)。
-
自定义异常扩展:继承BlockException实现自定义阻塞异常(如接口灰度发布拦截异常),在handle方法中新增对应处理逻辑。
-
响应体国际化:结合Spring MessageSource,根据请求头中的语言标识,返回多语言异常提示。