最近咱们的项目在考虑使用Gateway,考虑使用Spring Cloud Gateway,发现网关的异常处理和spring boot 单体应用异常处理仍是有很大区别的。让咱们来回顾一下异常。前端
关于异常是拿来干什么的,不少人老程序员认为就是拿来咱们Debug的时候排错的,固然这一点确实是异常机制很是大的一个好处,但异常机制包含着更多的意义。程序员
异常处理(又称为错误处理)功能提供了处理程序运行时出现的任何意外或异常状况的方法。异常处理使用 try、catch 和 finally 关键字来尝试可能未成功的操做,处理失败,以及在过后清理资源。异常根据意义成三种:业务、系统、代码异常,不一样的异常采用不一样的处理方式。具体的什么样的异常怎么处理就不说了。web
红线和绿线表明两条异常路径spring
1,红线表明:请求到Gateway发生异常,可能因为后端app在启动或者是没启动数据库
2,绿线表明:请求到Gateway转发到后端app,后端app发生异常,而后Gateway转发后端异常到前端后端
红线确定是走Gateway自定义异常:springboot
两个类的代码以下(参考:http://cxytiandi.com/blog/detail/20548):架构
1 @Configuration 2 @EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class}) 3 public class ExceptionHandlerConfiguration { 4 5 private final ServerProperties serverProperties; 6 7 private final ApplicationContext applicationContext; 8 9 private final ResourceProperties resourceProperties; 10 11 private final List<ViewResolver> viewResolvers; 12 13 private final ServerCodecConfigurer serverCodecConfigurer; 14 15 public ExceptionHandlerConfiguration(ServerProperties serverProperties, 16 ResourceProperties resourceProperties, 17 ObjectProvider<List<ViewResolver>> viewResolversProvider, 18 ServerCodecConfigurer serverCodecConfigurer, 19 ApplicationContext applicationContext) { 20 this.serverProperties = serverProperties; 21 this.applicationContext = applicationContext; 22 this.resourceProperties = resourceProperties; 23 this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList); 24 this.serverCodecConfigurer = serverCodecConfigurer; 25 } 26 27 @Bean 28 @Order(Ordered.HIGHEST_PRECEDENCE) 29 public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) { 30 JsonExceptionHandler exceptionHandler = new JsonExceptionHandler( 31 errorAttributes, 32 this.resourceProperties, 33 this.serverProperties.getError(), 34 this.applicationContext); 35 exceptionHandler.setViewResolvers(this.viewResolvers); 36 exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters()); 37 exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders()); 38 return exceptionHandler; 39 }
1 public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler { 2 3 private static Logger logger = LoggerFactory.getLogger(JsonExceptionHandler.class); 4 5 public JsonExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties, 6 ErrorProperties errorProperties, ApplicationContext applicationContext) { 7 super(errorAttributes, resourceProperties, errorProperties, applicationContext); 8 } 9 10 /** 11 * 获取异常属性 12 */ 13 @Override 14 protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) { 15 int code = HttpStatus.INTERNAL_SERVER_ERROR.value(); 16 Throwable error = super.getError(request); 17 if (error instanceof org.springframework.cloud.gateway.support.NotFoundException) { 18 code = HttpStatus.NOT_FOUND.value(); 19 } 20 return response(code, this.buildMessage(request, error)); 21 } 22 23 /** 24 * 指定响应处理方法为JSON处理的方法 25 * @param errorAttributes 26 */ 27 @Override 28 protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) { 29 return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse); 30 } 31 32 33 /** 34 * 根据code获取对应的HttpStatus 35 * @param errorAttributes 36 */ 37 @Override 38 protected HttpStatus getHttpStatus(Map<String, Object> errorAttributes) { 39 int statusCode = (int) errorAttributes.get("code"); 40 return HttpStatus.valueOf(statusCode); 41 } 42 43 /** 44 * 构建异常信息 45 * @param request 46 * @param ex 47 * @return 48 */ 49 private String buildMessage(ServerRequest request, Throwable ex) { 50 StringBuilder message = new StringBuilder("Failed to handle request ["); 51 message.append(request.methodName()); 52 message.append(" "); 53 message.append(request.uri()); 54 message.append("]"); 55 if (ex != null) { 56 message.append(": "); 57 message.append(ex.getMessage()); 58 } 59 return message.toString(); 60 } 61 62 /** 63 * 构建返回的JSON数据格式 64 * @param status 状态码 65 * @param errorMessage 异常信息 66 * @return 67 */ 68 public static Map<String, Object> response(int status, String errorMessage) { 69 Map<String, Object> map = new HashMap<>(); 70 map.put("code", status); 71 map.put("message", errorMessage); 72 map.put("data", null); 73 logger.error(map.toString()); 74 return map; 75 } 76 }
绿线表明Gateway转发异常app
转发的异常,确定是springboot单体中处理的,至于spring单体中的异常是怎么处理的呢?确定是用@ControllerAdvice去作。框架
1 @ExceptionHandler(value = Exception.class) 2 @ResponseBody 3 public AppResponse exceptionHandler(HttpServletRequest request, Exception e) { 4 String ip = RequestUtil.getIpAddress(request); 5 logger.info("调用者IP:" + ip); 6 String errorMessage = String.format("Url:[%s]%n{%s}", request.getRequestURL().toString(), e.getMessage()); 7 logger.error(errorMessage, e); 8 return AppResponse.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage()); 9 }
到这里基本上能够了,你们不要试着去用Gateway去捕获后端异常,回到最初的起点,API 网关(API Gateway)主要负责服务请求路由、组合及协议转换,异常一样也是同样,Gateway只负责转发单体应用的异常,不要试图Gateway捕获后端服务异常,而后再输出给前端。感谢猿天地的一句惊醒梦中人!