Spring Cloud 如何统一异常处理?写得太好了!

打印 上一主题 下一主题

主题 554|帖子 554|积分 1662

作者: BNDong
链接: https://www.cnblogs.com/bndong/p/10135370.html
前言

在启动应用时会发现在控制台打印的日志中出现了两个路径为 {[/error]} 的访问地址,当系统中发送异常错误时,Spring Boot 会根据请求方式分别跳转到以 JSON 格式或以界面显示的 /error 地址中显示错误信息。
  1. 2018-12-18 09:36:24.627  INFO 19040 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" ...
  2. 2018-12-18 09:36:24.632  INFO 19040 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" ...
复制代码
Spring Boot 基础就不介绍了,推荐下这个实战教程:
https://github.com/javastacks/spring-boot-best-practice
默认异常处理

使用 AJAX 方式请求时返回的 JSON 格式错误信息。
  1. {
  2.     "timestamp": "2018-12-18T01:50:51.196+0000",
  3.     "status": 404,
  4.     "error": "Not Found",
  5.     "message": "No handler found for GET /err404",
  6.     "path": "/err404"
  7. }
复制代码
使用浏览器请求时返回的错误信息界面。

自定义异常处理

引入依赖
  1. <dependency>
  2.     <groupId>com.alibaba</groupId>
  3.     <artifactId>fastjson</artifactId>
  4.     <version>1.2.54</version>
  5. </dependency>
  6. <dependency>
  7.     <groupId>org.springframework.boot</groupId>
  8.     <artifactId>spring-boot-starter-freemarker</artifactId>
  9. </dependency>
复制代码
fastjson 是 JSON 序列化依赖, spring-boot-starter-freemarker 是一个模板引擎,用于我们设置错误输出模板。
增加配置
  1. # 出现错误时, 直接抛出异常(便于异常统一处理,否则捕获不到404)
  2. spring.mvc.throw-exception-if-no-handler-found=true
  3. # 不要为工程中的资源文件建立映射
  4. spring.resources.add-mappings=false
复制代码
  1. spring:
  2.   # 出现错误时, 直接抛出异常(便于异常统一处理,否则捕获不到404)
  3.   mvc:
  4.     throw-exception-if-no-handler-found: true
  5.   # 不要为工程中的资源文件建立映射
  6.   resources:
  7.     add-mappings: false
复制代码
Spring Boot 基础就不介绍了,推荐下这个实战教程:
https://github.com/javastacks/spring-boot-best-practice
新建错误信息实体
  1. /**
  2. * 信息实体
  3. */
  4. public class ExceptionEntity implements Serializable {
  5.     private static final long serialVersionUID = 1L;
  6.     private String message;
  7.     private int    code;
  8.     private String error;
  9.     private String path;
  10.     @JSONField(format = "yyyy-MM-dd hh:mm:ss")
  11.     private Date timestamp = new Date();
  12.     public static long getSerialVersionUID() {
  13.         return serialVersionUID;
  14.     }
  15.     public String getMessage() {
  16.         return message;
  17.     }
  18.     public void setMessage(String message) {
  19.         this.message = message;
  20.     }
  21.     public int getCode() {
  22.         return code;
  23.     }
  24.     public void setCode(int code) {
  25.         this.code = code;
  26.     }
  27.     public String getError() {
  28.         return error;
  29.     }
  30.     public void setError(String error) {
  31.         this.error = error;
  32.     }
  33.     public String getPath() {
  34.         return path;
  35.     }
  36.     public void setPath(String path) {
  37.         this.path = path;
  38.     }
  39.     public Date getTimestamp() {
  40.         return timestamp;
  41.     }
  42.     public void setTimestamp(Date timestamp) {
  43.         this.timestamp = timestamp;
  44.     }
  45. }
复制代码
新建自定义异常
  1. /**
  2. * 自定义异常
  3. */
  4. public class BasicException extends RuntimeException {
  5.     private static final long serialVersionUID = 1L;
  6.     private int code = 0;
  7.     public BasicException(int code, String message) {
  8.         super(message);
  9.         this.code = code;
  10.     }
  11.     public int getCode() {
  12.         return this.code;
  13.     }
  14. }
复制代码
  1. /**
  2. * 业务异常
  3. */
  4. public class BusinessException extends BasicException {
  5.     private static final long serialVersionUID = 1L;
  6.     public BusinessException(int code, String message) {
  7.         super(code, message);
  8.     }
  9. }
复制代码
BasicException 继承了 RuntimeException ,并在原有的 Message 基础上增加了错误码 code 的内容。而 BusinessException 则是在业务中具体使用的自定义异常类,起到了对不同的异常信息进行分类的作用。
新建 error.ftl 模板文件

位置:/src/main/resources/templates/ 用于显示错误信息
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4.     <meta name="robots" content="noindex,nofollow" />
  5.     <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
  6.    
  7. </head>
  8. <body>
  9.     <h2>Exception Datas</h2>
  10.     <table>
  11.         <tbody>
  12.         <tr>
  13.             <td>Code</td>
  14.             <td>
  15.                 ${(exception.code)!}
  16.             </td>
  17.         </tr>
  18.         <tr>
  19.             <td>Time</td>
  20.             <td>
  21.                 ${(exception.timestamp?datetime)!}
  22.             </td>
  23.         </tr>
  24.         <tr>
  25.             <td>Path</td>
  26.             <td>
  27.                 ${(exception.path)!}
  28.             </td>
  29.         </tr>
  30.         <tr>
  31.             <td>Exception</td>
  32.             <td>
  33.                 ${(exception.error)!}
  34.             </td>
  35.         </tr>
  36.         <tr>
  37.             <td>Message</td>
  38.             <td>
  39.                 ${(exception.message)!}
  40.             </td>
  41.         </tr>
  42.         </tbody>
  43.     </table>
  44. </body>
  45. </html>
复制代码
编写全局异常控制类
  1. /**
  2. * 全局异常控制类
  3. */
  4. @ControllerAdvice
  5. public class GlobalExceptionHandler {
  6.     /**
  7.      * 404异常处理
  8.      */
  9.     @ExceptionHandler(value = NoHandlerFoundException.class)
  10.     @ResponseStatus(HttpStatus.NOT_FOUND)
  11.     public ModelAndView errorHandler(HttpServletRequest request, NoHandlerFoundException exception, HttpServletResponse response) {
  12.         return commonHandler(request, response,
  13.                 exception.getClass().getSimpleName(),
  14.                 HttpStatus.NOT_FOUND.value(),
  15.                 exception.getMessage());
  16.     }
  17.     /**
  18.      * 405异常处理
  19.      */
  20.     @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
  21.     public ModelAndView errorHandler(HttpServletRequest request, HttpRequestMethodNotSupportedException exception, HttpServletResponse response) {
  22.         return commonHandler(request, response,
  23.                 exception.getClass().getSimpleName(),
  24.                 HttpStatus.METHOD_NOT_ALLOWED.value(),
  25.                 exception.getMessage());
  26.     }
  27.     /**
  28.      * 415异常处理
  29.      */
  30.     @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
  31.     public ModelAndView errorHandler(HttpServletRequest request, HttpMediaTypeNotSupportedException exception, HttpServletResponse response) {
  32.         return commonHandler(request, response,
  33.                 exception.getClass().getSimpleName(),
  34.                 HttpStatus.UNSUPPORTED_MEDIA_TYPE.value(),
  35.                 exception.getMessage());
  36.     }
  37.     /**
  38.      * 500异常处理
  39.      */
  40.     @ExceptionHandler(value = Exception.class)
  41.     public ModelAndView errorHandler (HttpServletRequest request, Exception exception, HttpServletResponse response) {
  42.         return commonHandler(request, response,
  43.                 exception.getClass().getSimpleName(),
  44.                 HttpStatus.INTERNAL_SERVER_ERROR.value(),
  45.                 exception.getMessage());
  46.     }
  47.     /**
  48.      * 业务异常处理
  49.      */
  50.     @ExceptionHandler(value = BasicException.class)
  51.     private ModelAndView errorHandler (HttpServletRequest request, BasicException exception, HttpServletResponse response) {
  52.         return commonHandler(request, response,
  53.                 exception.getClass().getSimpleName(),
  54.                 exception.getCode(),
  55.                 exception.getMessage());
  56.     }
  57.     /**
  58.      * 表单验证异常处理
  59.      */
  60.     @ExceptionHandler(value = BindException.class)
  61.     @ResponseBody
  62.     public ExceptionEntity validExceptionHandler(BindException exception, HttpServletRequest request, HttpServletResponse response) {
  63.         List<FieldError> fieldErrors = exception.getBindingResult().getFieldErrors();
  64.         Map<String,String> errors = new HashMap<>();
  65.         for (FieldError error:fieldErrors) {
  66.             errors.put(error.getField(), error.getDefaultMessage());
  67.         }
  68.         ExceptionEntity entity = new ExceptionEntity();
  69.         entity.setMessage(JSON.toJSONString(errors));
  70.         entity.setPath(request.getRequestURI());
  71.         entity.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
  72.         entity.setError(exception.getClass().getSimpleName());
  73.         response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
  74.         return entity;
  75.     }
  76.     /**
  77.      * 异常处理数据处理
  78.      */
  79.     private ModelAndView commonHandler (HttpServletRequest request, HttpServletResponse response,
  80.                                             String error, int httpCode, String message) {
  81.         ExceptionEntity entity = new ExceptionEntity();
  82.         entity.setPath(request.getRequestURI());
  83.         entity.setError(error);
  84.         entity.setCode(httpCode);
  85.         entity.setMessage(message);
  86.         return determineOutput(request, response, entity);
  87.     }
  88.     /**
  89.      * 异常输出处理
  90.      */
  91.     private ModelAndView determineOutput(HttpServletRequest request, HttpServletResponse response, ExceptionEntity entity) {
  92.         if (!(
  93.                 request.getHeader("accept").contains("application/json")
  94.                 || (request.getHeader("X-Requested-With") != null && request.getHeader("X-Requested-With").contains("XMLHttpRequest"))
  95.         )) {
  96.             ModelAndView modelAndView = new ModelAndView("error");
  97.             modelAndView.addObject("exception", entity);
  98.             return modelAndView;
  99.         } else {
  100.             response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
  101.             response.setCharacterEncoding("UTF8");
  102.             response.setHeader("Content-Type", "application/json");
  103.             try {
  104.                 response.getWriter().write(ResultJsonTools.build(
  105.                         ResponseCodeConstant.SYSTEM_ERROR,
  106.                         ResponseMessageConstant.APP_EXCEPTION,
  107.                         JSONObject.parseObject(JSON.toJSONString(entity))
  108.                 ));
  109.             } catch (IOException e) {
  110.                 e.printStackTrace();
  111.             }
  112.             return null;
  113.         }
  114.     }
  115. }
复制代码
@ControllerAdvice
作用于类上,用于标识该类用于处理全局异常。
@ExceptionHandler
作用于方法上,用于对拦截的异常类型进行处理。value 属性用于指定具体的拦截异常类型,如果有多个 ExceptionHandler 存在,则需要指定不同的 value 类型,由于异常类拥有继承关系,所以 ExceptionHandler 会首先执行在继承树中靠前的异常类型。
BindException
该异常来自于表单验证框架 Hibernate validation,当字段验证未通过时会抛出此异常。
编写测试 Controller
  1. @RestController
  2. public class TestController {
  3.     @RequestMapping(value = "err")
  4.     public void error(){
  5.         throw new BusinessException(400, "业务异常错误信息");
  6.     }
  7.     @RequestMapping(value = "err2")
  8.     public void error2(){
  9.         throw new NullPointerException("手动抛出异常信息");
  10.     }
  11.     @RequestMapping(value = "err3")
  12.     public int error3(){
  13.         int a = 10 / 0;
  14.         return a;
  15.     }
  16. }
复制代码
使用 AJAX 方式请求时返回的 JSON 格式错误信息。
  1. # /err
  2. {
  3.     "msg": "应用程序异常",
  4.     "code": -1,
  5.     "status_code": 0,
  6.     "data": {
  7.         "path": "/err",
  8.         "code": 400,
  9.         "error": "BusinessException",
  10.         "message": "业务异常错误信息",
  11.         "timestamp": "2018-12-18 11:09:00"
  12.     }
  13. }
  14. # /err2
  15. {
  16.     "msg": "应用程序异常",
  17.     "code": -1,
  18.     "status_code": 0,
  19.     "data": {
  20.         "path": "/err2",
  21.         "code": 500,
  22.         "error": "NullPointerException",
  23.         "message": "手动抛出异常信息",
  24.         "timestamp": "2018-12-18 11:15:15"
  25.     }
  26. }
  27. # /err3
  28. {
  29.     "msg": "应用程序异常",
  30.     "code": -1,
  31.     "status_code": 0,
  32.     "data": {
  33.         "path": "/err3",
  34.         "code": 500,
  35.         "error": "ArithmeticException",
  36.         "message": "/ by zero",
  37.         "timestamp": "2018-12-18 11:15:46"
  38.     }
  39. }
  40. # /err404
  41. {
  42.     "msg": "应用程序异常",
  43.     "code": -1,
  44.     "status_code": 0,
  45.     "data": {
  46.         "path": "/err404",
  47.         "code": 404,
  48.         "error": "NoHandlerFoundException",
  49.         "message": "No handler found for GET /err404",
  50.         "timestamp": "2018-12-18 11:16:11"
  51.     }
  52. }
复制代码
使用浏览器请求时返回的错误信息界面。




示例代码:https://github.com/BNDong/spring-cloud-examples/tree/master/spring-cloud-zuul/cloud-zuul
参考资料:
近期热文推荐:
1.1,000+ 道 Java面试题及答案整理(2022最新版)
2.劲爆!Java 协程要来了。。。
3.Spring Boot 2.x 教程,太全了!
4.别再写满屏的爆爆爆炸类了,试试装饰器模式,这才是优雅的方式!!
5.《Java开发手册(嵩山版)》最新发布,速速下载!
觉得不错,别忘了随手点赞+转发哦!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

汕尾海湾

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表