最近我们的项目在考虑使用 Gateway, 考虑使用 Spring Cloud Gateway, 发现网关的异常处理和 spring boot 单体应用异常处理还是有很大区别的. 让我们来回顾一下异常.
关于异常是拿来干什么的, 很多人老程序员认为就是拿来我们 Debug 的时候排错的, 当然这一点确实是异常机制非常大的一个好处, 但异常机制包含着更多的意义.
关注业务实现. 异常机制使得业务代码与异常处理代码可以分开, 你可以将一些你调用数据库操作的代码写在一个方法里而只需要在方法上加上 throw DB 相关的异常. 至于如何处理它, 你可以在调用该方法的时候处理或者甚至选择不处理, 而不是直接在该方法内部添加上 if 判断如果数据库操作错误该如何办, 这样业务代码会非常混乱.
统一异常处理. 与上一点有所联系. 我当前所在项目的实践是, 自定义业务类异常, 在 Controller 或 Service 中抛出, 让后使用 Spring 提供的异常接口统一处理我们自己在内部抛出的异常. 这样一个异常处理架构就非常明了.
程序的健壮性. 如果没有异常机制, 那么来了个对空对象的某方法调用怎么办呢? 直接让程序挂掉? 这令人无法接受, 当然, 我们自己平时写的一些小的东西确实是这样, 没有处理它, 让后程序挂了. 但在 web 框架中, 可以利用异常处理机制捕获该异常并将错误信息传递给我们然后继续处理下个请求. 所以异常对于健壮性是非常有帮助的.
异常处理 (又称为错误处理) 功能提供了处理程序运行时出现的任何意外或异常情况的方法. 异常处理使用 try,catch 和 finally 关键字来尝试可能未成功的操作, 处理失败, 以及在事后清理资源. 异常根据意义成三种: 业务, 系统, 代码异常, 不同的异常采用不同的处理方式. 具体的什么样的异常怎么处理就不说了.
红线和绿线代表两条异常路径
1, 红线代表: 请求到 Gateway 发生异常, 可能由于后端 App 在启动或者是没启动
2, 绿线代表: 请求到 Gateway 转发到后端 App, 后端 App 发生异常, 然后 Gateway 转发后端异常到前端
红线肯定是走 Gateway 自定义异常:
两个类的代码如下(参考: http://cxytiandi.com/blog/detail/20548):
- @Configuration
- @EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
- public class ExceptionHandlerConfiguration {
- private final ServerProperties serverProperties;
- private final ApplicationContext applicationContext;
- private final ResourceProperties resourceProperties;
- private final List<ViewResolver> viewResolvers;
- private final ServerCodecConfigurer serverCodecConfigurer;
- public ExceptionHandlerConfiguration(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)
- public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
- JsonExceptionHandler exceptionHandler = new JsonExceptionHandler(
- errorAttributes,
- this.resourceProperties,
- this.serverProperties.getError(),
- this.applicationContext);
- exceptionHandler.setViewResolvers(this.viewResolvers);
- exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
- exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
- return exceptionHandler;
- }
- public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {
- private static Logger logger = LoggerFactory.getLogger(JsonExceptionHandler.class);
- public JsonExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties,
- ErrorProperties errorProperties, ApplicationContext applicationContext) {
- super(errorAttributes, resourceProperties, errorProperties, applicationContext);
- }
- /**
- * 获取异常属性
- */
- @Override
- protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
- int code = HttpStatus.INTERNAL_SERVER_ERROR.value();
- Throwable error = super.getError(request);
- if (error instanceof org.springframework.cloud.gateway.support.NotFoundException) {
- code = HttpStatus.NOT_FOUND.value();
- }
- return response(code, this.buildMessage(request, error));
- }
- /**
- * 指定响应处理方法为 JSON 处理的方法
- * @param errorAttributes
- */
- @Override
- protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
- return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
- }
- /**
- * 根据 code 获取对应的 HttpStatus
- * @param errorAttributes
- */
- @Override
- protected HttpStatus getHttpStatus(Map<String, Object> errorAttributes) {
- int statusCode = (int) errorAttributes.get("code");
- return HttpStatus.valueOf(statusCode);
- }
- /**
- * 构建异常信息
- * @param request
- * @param ex
- * @return
- */
- private String buildMessage(ServerRequest request, Throwable ex) {
- StringBuilder message = new StringBuilder("Failed to handle request [");
- message.append(request.methodName());
- message.append(" ");
- message.append(request.uri());
- message.append("]");
- if (ex != null) {
- message.append(":");
- message.append(ex.getMessage());
- }
- return message.toString();
- }
- /**
- * 构建返回的 JSON 数据格式
- * @param status 状态码
- * @param errorMessage 异常信息
- * @return
- */
- public static Map<String, Object> response(int status, String errorMessage) {
- Map<String, Object> map = new HashMap<>();
- map.put("code", status);
- map.put("message", errorMessage);
- map.put("data", null);
- logger.error(map.toString());
- return map;
- }
- }
绿线代表 Gateway 转发异常
转发的异常, 肯定是 springboot 单体中处理的, 至于 spring 单体中的异常是怎么处理的呢? 肯定是用 @ControllerAdvice 去做.
- @ExceptionHandler(value = Exception.class)
- @ResponseBody
- public AppResponse exceptionHandler(HttpServletRequest request, Exception e) {
- String ip = RequestUtil.getIpAddress(request);
- logger.info("调用者 IP:" + ip);
- String errorMessage = String.format("Url:[%s]%n{%s}", request.getRequestURL().toString(), e.getMessage());
- logger.error(errorMessage, e);
- return AppResponse.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage());
- }
到这里基本上可以了, 大家不要试着去用 Gateway 去捕获后端异常, 回到最初的起点, API 网关 (API Gateway) 主要负责服务请求路由, 组合及协议转换, 异常同样也是一样, Gateway 只负责转发单体应用的异常, 不要试图 Gateway 捕获后端服务异常, 然后再输出给前端. 感谢猿天地的一句惊醒梦中人!
来源: https://www.cnblogs.com/viaiu/p/10403557.html