异常的处理在我们的日常开发中是一个绕不过去的坎,在Spring Boot 项目中如何优雅的去处理异常,正是我们这一节课需要研究的方向。

在一个Spring Boot项目中,我们可以把异常分为两种,第一种是请求到达Controller层之前,第二种是到达Controller层之后项目代码中发生的错误。而第一种又可以分为两种错误类型:1. 路径错误 2. 类似于请求方式错误,参数类型不对等类似错误。

为了保持返回值的统一,我们这里定义了统一返回的类ReturnVO,以及一个记录错误返回码和错误信息的枚举类ReturnCode,而具体的错误信息和错误代码保存到了response.properties中,使用流进行读取。

  1. public class ReturnVO {
  2. private static Properties properties = ReadPropertiesUtil.getProperties(System.getProperty("user.dir") + CommonUrl.RESPONSE_PROP_URL);
  3. /**
  4. * 返回代码
  5. */
  6. private String code;
  7. /**
  8. * 返回信息
  9. */
  10. private String message;
  11. /**
  12. * 返回数据
  13. */
  14. private Object data;
  15. public Object getData() {
  16. return data;
  17. }
  18. public void setData(Object data) {
  19. this.data = data;
  20. }
  21. public String getMessage() {
  22. return message;
  23. }
  24. public void setMessage(String message) {
  25. this.message = message;
  26. }
  27. public String getCode() {
  28. return code;
  29. }
  30. public void setCode(String code) {
  31. this.code = code;
  32. }
  33. /**
  34. * 默认构造,返回操作正确的返回代码和信息
  35. */
  36. public ReturnVO() {
  37. this.setCode(properties.getProperty(ReturnCode.SUCCESS.val()));
  38. this.setMessage(properties.getProperty(ReturnCode.SUCCESS.msg()));
  39. }
  40. /**
  41. * 返回代码,这里需要在枚举中去定义
  42. * @param code
  43. */
  44. public ReturnVO(ReturnCode code) {
  45. this.setCode(properties.getProperty(code.val()));
  46. this.setMessage(properties.getProperty(code.msg()));
  47. }
  48. /**
  49. * 返回数据,默认返回正确的code和message
  50. * @param data
  51. */
  52. public ReturnVO(Object data) {
  53. this.setCode(properties.getProperty(ReturnCode.SUCCESS.val()));
  54. this.setMessage(properties.getProperty(ReturnCode.SUCCESS.msg()));
  55. this.setData(data);
  56. }
  57. /**
  58. * 返回错误的代码,以及自定义的错误信息
  59. * @param code
  60. * @param message
  61. */
  62. public ReturnVO(ReturnCode code, String message) {
  63. this.setCode(properties.getProperty(code.val()));
  64. this.setMessage(message);
  65. }
  66. /**
  67. * 返回自定义的code,message,以及data
  68. * @param code
  69. * @param message
  70. * @param data
  71. */
  72. public ReturnVO(ReturnCode code, String message, Object data) {
  73. this.setCode(code.val());
  74. this.setMessage(message);
  75. this.setData(data);
  76. }
  77. @Override
  78. public String toString() {
  79. return "ReturnVO{" +
  80. "code='" + code + '\'' +
  81. ", message='" + message + '\'' +
  82. ", data=" + data +
  83. '}';
  84. }
  85. }

其他的错误处理只需要在枚举类中添加对应的异常即可,枚举的名称要定义为异常的名称,这样可以直接不用对其他的代码进行修改,添加一个新的异常时,仅仅添加枚举类中的字段和properties文件中的属性。

  1. public enum ReturnCode {
  2. /** 操作成功 */
  3. SUCCESS("SUCCESS_CODE", "SUCCESS_MSG"),
  4. /** 操作失败 */
  5. FAIL("FAIL_CODE", "FAIL_MSG"),
  6. /** 空指针异常 */
  7. NullPointerException("NPE_CODE", "NPE_MSG"),
  8. /** 自定义异常之返回值为空 */
  9. NullResponseException("NRE_CODE", "NRE_MSG"),
  10. /** 运行时异常 */
  11. RuntimeException("RTE_CODE","RTE_MSG"),
  12. /** 请求方式错误异常 */
  13. HttpRequestMethodNotSupportedException("REQUEST_METHOD_UNSUPPORTED_CODE","REQUEST_METHOD_UNSUPPORTED_MSG"),
  14. /** INTERNAL_ERROR */
  15. BindException("BIND_EXCEPTION_CODE","BIND_EXCEPTION_MSG"),
  16. /** 页面路径不对 */
  17. UrlError("UE_CODE","UE_MSG");
  18. private ReturnCode(String value, String msg){
  19. this.val = value;
  20. this.msg = msg;
  21. }
  22. public String val() {
  23. return val;
  24. }
  25. public String msg() {
  26. return msg;
  27. }
  28. private String val;
  29. private String msg;
  30. }

这里我自定义了一些异常用于后面的测试,在我们实际的项目中需要定义很多的异常去完善。

  1. SUCCESS_CODE=2000
  2. SUCCESS_MSG=操作成功
  3. FAIL_CODE=5000
  4. FAIL_MSG=操作失败
  5. NPE_CODE=5001
  6. NPE_MSG=空指针异常
  7. NRE_CODE=5002
  8. NRE_MSG=返回值为空
  9. RTE_CODE=5001
  10. RTE_MSG=运行时异常
  11. UE_CODE=404
  12. UE_MSG=页面路径有误
  13. REQUEST_METHOD_UNSUPPORTED_CODE=4000
  14. REQUEST_METHOD_UNSUPPORTED_MSG=请求方式异常
  15. BIND_EXCEPTION_CODE=4001
  16. BIND_EXCEPTION_MSG=请求参数绑定失败

这里的路径错误处理方式是采用了实现ErrorController接口,然后实现了getErrorPath()方法:

  1. /**
  2. * 请求路径有误
  3. * @author yangwei
  4. * @since 2019-01-02 18:13
  5. */
  6. @RestController
  7. public class RequestExceptionHandler implements ErrorController {
  8. @Override
  9. public String getErrorPath() {
  10. return "/error";
  11. }
  12. @RequestMapping("/error")
  13. public ReturnVO errorPage(){
  14. return new ReturnVO(ReturnCode.UrlError);
  15. }
  16. }

这里可以进行测试一下:

类似于到达Controller之前的请求参数错误,请求方式错误,数据格式不对等等错误都归类为一种,这里仅仅展示请求方式错误的处理方式。

  1. /**
  2. * 全局异常处理类
  3. * @author yangwei
  4. *
  5. * 用于全局返回json,如需返回ModelAndView请使用ControllerAdvice
  6. * 继承了ResponseEntityExceptionHandler,对于一些类似于请求方式异常的异常进行捕获
  7. */
  8. @RestControllerAdvice
  9. public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
  10. private static Properties properties = ReadPropertiesUtil.getProperties(System.getProperty("user.dir") + CommonUrl.RESPONSE_PROP_URL);
  11. /**
  12. * 重写handleExceptionInternal,自定义处理过程
  13. **/
  14. @Override
  15. protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
  16. //这里将异常直接传给handlerException()方法进行处理,返回值为OK保证友好的返回,而不是出现500错误码。
  17. return new ResponseEntity<>(handlerException(ex), HttpStatus.OK);
  18. }
  19. /**
  20. * 异常捕获
  21. * @param e 捕获的异常
  22. * @return 封装的返回对象
  23. **/
  24. @ExceptionHandler(Exception.class)
  25. public ReturnVO handlerException(Throwable e) {
  26. ReturnVO returnVO = new ReturnVO();
  27. String errorName = e.getClass().getName();
  28. errorName = errorName.substring(errorName.lastIndexOf(".") + 1);
  29. //如果没有定义异常,而是直接抛出一个运行时异常,需要进入以下分支
  30. if (e.getClass() == RuntimeException.class) {
  31. returnVO.setMessage(properties.getProperty(valueOf("RuntimeException").msg()) +": "+ e.getMessage());
  32. returnVO.setCode(properties.getProperty(valueOf("RuntimeException").val()));
  33. } else {
  34. returnVO.setMessage(properties.getProperty(valueOf(errorName).msg()));
  35. returnVO.setCode(properties.getProperty(valueOf(errorName).val()));
  36. }
  37. return returnVO;
  38. }
  39. }

这里我们可以进行测试:

  1. @RestController
  2. @RequestMapping(value = "/user")
  3. public class UserController {
  4. @Autowired
  5. private IUserService userService;
  6. @PostMapping(value = "/findAll")
  7. public Object findAll() {
  8. throw new RuntimeException("ddd");
  9. }
  10. @RequestMapping(value = "/findAll1")
  11. public ReturnVO findAll1(UserDO userDO) {
  12. System.out.println(userDO);
  13. return new ReturnVO(userService.findAll1());
  14. }
  15. @RequestMapping(value = "/test")
  16. public ReturnVO test() {
  17. throw new RuntimeException("测试非自定义运行时异常");
  18. }
  19. }

直接在浏览器访问findAll,默认为get方法,这里按照我们期望会抛出请求方式异常的错误:

访问findAll1?id=123ss,这里由于我们接受的UserDOid属性是Integer类型,所以这里报一个参数绑定异常:

访问test,测试非自定义运行时异常:

我们上节课使用AOP对于全局异常处理进行了一次简单的操作,这节课进行了完善,并将其放入到我们的公用模块,使用时只需导入jar包,然后在启动类配置扫描包路径即可

  1. /**
  2. * 统一封装返回值和异常处理
  3. *
  4. * @author vi
  5. * @since 2018/12/20 6:09 AM
  6. */
  7. @Slf4j
  8. @Aspect
  9. @Order(5)
  10. @Component
  11. public class ResponseAop {
  12. @Autowired
  13. private GlobalExceptionHandler exceptionHandler;
  14. /**
  15. * 切点
  16. */
  17. @Pointcut("execution(public * indi.viyoung.viboot.*.controller..*(..))")
  18. public void httpResponse() {
  19. }
  20. /**
  21. * 环切
  22. */
  23. @Around("httpResponse()")
  24. public ReturnVO handlerController(ProceedingJoinPoint proceedingJoinPoint) {
  25. ReturnVO returnVO = new ReturnVO();
  26. try {
  27. Object proceed = proceedingJoinPoint.proceed();
  28. if (proceed instanceof ReturnVO) {
  29. returnVO = (ReturnVO) proceed;
  30. } else {
  31. returnVO.setData(proceed);
  32. }
  33. } catch (Throwable throwable) {
  34. // 这里直接调用刚刚我们在handler中编写的方法
  35. returnVO = exceptionHandler.handlerException(throwable);
  36. }
  37. return returnVO;
  38. }
  39. }

做完这些准备工作,以后我们在进行异常处理的时候只需要进行以下几步操作:

  • 引入公用模块jar包
  • 在启动类上配置扫描包路径
  • 如果新增异常的话,在枚举类中新增后,再去properties中进行返回代码和返回信息的编辑即可(注意:枚举类的变量名一定要和异常名保持一致

版权声明:本文为viyoung原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/viyoung/p/10218343.html