gateWay全局异常配置-----代码+分析思路
gateWay全局异常配置-----代码+分析思路
每天多学一点点~
话不多说,这就开始吧…
1.前言
最近一周在弄spring-cloud的基础架构,试着在网关这一层做统一的异常处理,返回统一信息,这样对前端比较友好。一开始无头绪,试着慢慢分析,这里做一下总结。
2.思考思路
说到底网关也是个单体的springboot项目,springboot是自动装配,在没有引入网关之前,其处理统一异常的类在
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
而 网关 底层 是 webflux 的响应式编程,于是,找到了 reactive是包
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration(接触spring比较多的童鞋,一般都会直接看AutoConfigurationxxxx),
webflux的错误web自动装配,点进去这个类,发现如下方法 errorWebExceptionHandler,
@Bean
@ConditionalOnMissingBean(value = ErrorWebExceptionHandler.class, search = SearchStrategy.CURRENT)
@Order(-1)
public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
// 用的是DefaultErrorWebExceptionHandler,重写后注入我们自定义的ErrorWebExceptionHandler
DefaultErrorWebExceptionHandler exceptionHandler = new DefaultErrorWebExceptionHandler(errorAttributes,
this.resourceProperties, this.serverProperties.getError(), this.applicationContext);
exceptionHandler.setViewResolvers(this.viewResolvers);
exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
return exceptionHandler;
}
打个断点,模拟一下异常,发现 果真 断点进来了,最后一步步调试,发现其调用了
org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler#renderErrorResponse方法。
于是乎便有了思路,如下:
- 复制ErrorWebFluxAutoConfiguration 类,名称为 ExceptionAutoConfiguration,重写其errorWebExceptionHandler方法 ,然后定义自己的CustomErrorWebExceptionHandler
- 写一个自己的CustomErrorWebExceptionHandler类, 继承 DefaultErrorWebExceptionHandler 的 renderErrorResponse()方法和getRoutingFunction() 。(原本DefaultErrorWebExceptionHandler中这两个方法都是返回错误页面的,我们这里只需要重新塞入自定义的handler就行)
- 自定义GateWayExceptionHandlerAdvice,返回统一json格式
3.代码实现
GateWayExceptionHandlerAdvice 类
/* ━━━━━━佛祖保佑━━━━━━
* ,;,,;
* ,;;'( 社
* __ ,;;' ' \ 会
* /' '\'~~'~' \ /'\.) 主
* ,;( ) / |. 义
*,;' \ /-.,,( ) \ 码
* ) / ) / )| 农
* || || \)
* (_\ (_\
* ━━━━━━永无BUG━━━━━━
* @author :zjq
* @date :2020/7/21 16:14
* @description: TODO 自定义网关异常
* @version: V1.0
* @slogan: 天下风云出我辈,一入代码岁月催
*/
@Slf4j
@Component
public class GateWayExceptionHandlerAdvice {
/**
* 统一 异常
*
* @param throwable
* @return
*/
@ExceptionHandler(value = {Exception.class})
public CommonResult handle(Throwable throwable) {
if (throwable instanceof SignatureException) {
return signHandle((SignatureException) throwable);
} else if (throwable instanceof NotFoundException) {
return notFoundHandle((NotFoundException) throwable);
} else if (throwable instanceof ResponseStatusException) {
return handle((ResponseStatusException) throwable);
} else if (throwable instanceof GateWayException) {
return badGatewayHandle((GateWayException) throwable);
} else if (throwable instanceof ConnectTimeoutException) {
return timeoutHandle((ConnectTimeoutException) throwable);
} else {
return CommonResult.failed();
}
}
/**
* 401 校验 异常
*
* @param ex
* @return
*/
@ExceptionHandler(value = {SignatureException.class})
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public CommonResult signHandle(SignatureException ex) {
log.error("SignatureException:{}", ex.getMessage());
return CommonResult.failed(ResultCode.UNAUTHORIZED);
}
/**
* 404 服务未找到 异常
*
* @param ex
* @return
*/
@ExceptionHandler(value = {NotFoundException.class})
@ResponseStatus(HttpStatus.NOT_FOUND)
public CommonResult notFoundHandle(NotFoundException ex) {
log.error("not found exception:{}", ex.getMessage());
return CommonResult.failed(ResultCode.NOT_FOUND);
}
/**
* 500 其他服务 异常
*
* @param ex
* @return
*/
@ExceptionHandler(value = {ResponseStatusException.class})
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public CommonResult handle(ResponseStatusException ex) {
log.error("ResponseStatusException:{}", ex.getMessage());
return CommonResult.failed(ResultCode.UNAUTHORIZED);
}
/**
* 502 错误网关 异常
*
* @param ex
* @return
*/
@ExceptionHandler(value = {GateWayException.class})
@ResponseStatus(HttpStatus.BAD_GATEWAY)
public CommonResult badGatewayHandle(GateWayException ex) {
log.error("badGateway exception:{}", ex.getMessage());
return CommonResult.failed(ResultCode.BAD_GATEWAY);
}
/**
* 504 网关超时 异常
*
* @param ex
* @return
*/
@ExceptionHandler(value = {ConnectTimeoutException.class})
@ResponseStatus(HttpStatus.GATEWAY_TIMEOUT)
public CommonResult timeoutHandle(ConnectTimeoutException ex) {
log.error("connect timeout exception:{}", ex.getMessage());
return CommonResult.failed(ResultCode.GATEWAY_CONNECT_TIME_OUT);
}
}
CustomErrorWebExceptionHandler 类
/* ━━━━━━佛祖保佑━━━━━━
* ,;,,;
* ,;;'( 社
* __ ,;;' ' \ 会
* /' '\'~~'~' \ /'\.) 主
* ,;( ) / |. 义
*,;' \ /-.,,( ) \ 码
* ) / ) / )| 农
* || || \)
* (_\ (_\
* ━━━━━━永无BUG━━━━━━
* @author :zjq
* @date :2020/7/21 16:14
* @description: TODO 重写webflux 的 DefaultErrorWebExceptionHandler
* @version: V1.0
* @slogan: 天下风云出我辈,一入代码岁月催
*/
@Slf4j
public class CustomErrorWebExceptionHandler extends DefaultErrorWebExceptionHandler {
@Autowired
private GateWayExceptionHandlerAdvice gateWayExceptionHandlerAdvice;
public CustomErrorWebExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties,
ErrorProperties errorProperties, ApplicationContext applicationContext) {
super(errorAttributes, resourceProperties, errorProperties, applicationContext);
}
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
/**
* 重写 异常方法 塞入自己的 gateWayExceptionHandlerAdvice
*
* @param request
* @return
*/
@Override
protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
boolean includeStackTrace = isIncludeStackTrace(request, MediaType.ALL);
Map<String, Object> error = getErrorAttributes(request, includeStackTrace);
Throwable throwable = getError(request);
return ServerResponse.status(super.getHttpStatus(error))
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(BodyInserters.fromObject(gateWayExceptionHandlerAdvice.handle(throwable)));
}
}
ExceptionAutoConfiguration 类
/* ━━━━━━佛祖保佑━━━━━━
* ,;,,;
* ,;;'( 社
* __ ,;;' ' \ 会
* /' '\'~~'~' \ /'\.) 主
* ,;( ) / |. 义
*,;' \ /-.,,( ) \ 码
* ) / ) / )| 农
* || || \)
* (_\ (_\
* ━━━━━━永无BUG━━━━━━
* @author :zjq
* @date :2020/7/21 16:14
* @description: TODO 观察 网关 ErrorWebFluxAutoConfiguration 的配置 重写
* 把该该配置类copy下来,然后定义errorWebExceptionHandler的逻辑
* 写一个自定义的异常处理器
* @version: V1.0
* @slogan: 天下风云出我辈,一入代码岁月催
*/
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@ConditionalOnClass(WebFluxConfigurer.class)
@AutoConfigureBefore(WebFluxAutoConfiguration.class)
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
public class ExceptionAutoConfiguration {
private ServerProperties serverProperties;
private ApplicationContext applicationContext;
private ResourceProperties resourceProperties;
private List<ViewResolver> viewResolvers;
private ServerCodecConfigurer serverCodecConfigurer;
public ExceptionAutoConfiguration(ServerProperties serverProperties,
ResourceProperties resourceProperties,
ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer,
ApplicationContext applicationContext) {
this.serverProperties = serverProperties;
this.applicationContext = applicationContext;
this.resourceProperties = resourceProperties;
this.viewResolvers = viewResolversProvider
.getIfAvailable(() -> Collections.emptyList());
this.serverCodecConfigurer = serverCodecConfigurer;
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE) // 要比 ErrorWebFluxAutoConfiguration 小,表示其优先调用
public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
// 注入自己的 CustomErrorWebExceptionHandler 处理类
DefaultErrorWebExceptionHandler exceptionHandler = new CustomErrorWebExceptionHandler(
errorAttributes, this.resourceProperties,
this.serverProperties.getError(), this.applicationContext);
exceptionHandler.setViewResolvers(this.viewResolvers);
exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
return exceptionHandler;
}
}
4.测试
服务 | 端口 |
---|---|
boke-gateway | 8888 |
boke-api(业务工程) | 8081 |
若网关工程这里出现异常,比如(下游工程未开启, System.out.println(1/0); 等等)会返回自定义json格式,出现了自定义异常。
但是,下游服务出现异常,依然会出现网关原生的格式,比如
boke-api 工程
@RequestMapping("/api")
@RestController
public class BokeApiController extends BaseController {
@PostMapping("/getLog")
public CommonResult getLog(@RequestBody ApiReq req) {
System.out.println(1/0);
return CommonResult.success(null, "调用成功");
}
}
所以,在下游服务 boke-api 工程 中加上 @RestControllerAdvice
/* ━━━━━━佛祖保佑━━━━━━
* ,;,,;
* ,;;'( 社
* __ ,;;' ' \ 会
* /' '\'~~'~' \ /'\.) 主
* ,;( ) / |. 义
*,;' \ /-.,,( ) \ 码
* ) / ) / )| 农
* || || \)
* (_\ (_\
* ━━━━━━永无BUG━━━━━━
* @author :zjq
* @date :2020/7/21 01:39
* @description: TODO 接口层 是否需要通用接口 后续看
* @version: V1.0
* @slogan: 天下风云出我辈,一入代码岁月催
*/
@RestControllerAdvice
public class RequestBadExceptionHandler {
/**
* 兜底异常捕捉
*
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
public CommonResult<String> ExepitonHandler(Exception e) {
if (e instanceof NoHandlerFoundException) {
return CommonResult.failed(ResultCode.NOT_FOUND);
} else if (e instanceof BusinessException) {
return CommonResult.failed("api接口错误: " + e.getMessage());
}
return CommonResult.failed();
}
}
再次进行测试,出现自定义异常。即,每个下游微服务,依然要配置springboot的全局异常
5.结语
世上无难事,只怕有心人,每天积累一点点,fighting!!!
本文地址:https://blog.csdn.net/weixin_42437633/article/details/107595805