日常开发过程中,难免有的程序会因为某些原因抛出异常,而这些异常一般都是利用try ,catch的方式处理异常或者throw,throws的方式抛出异常不管。这种方法对于程序员来说处理也比较麻烦,对客户来说也不太友好,所以我们希望既能方便程序员编写代码,不用过多的自己去处理各种异常编写重复的代码又能提升用户的体验,这时候全局异常处理就显得很重要也很便捷了,是一种不错的选择。

一、 全局异常捕获与处理’

Springboot对于异常的处理做了不错的支持,它提供了两个可用的注解。
@ControllerAdvice:用来开启全局的异常捕获
@ExceptionHandler:说明捕获哪些异常,对哪些异常进行处理。

  1. @ControllerAdvice
  2. public class MyExceptionHandler {
  3. @ExceptionHandler(value =Exception.class)
  4. public String exceptionHandler(Exception e){
  5. System.out.println("发生了一个异常"+e);
  6. return e.getMessage();
  7. }
  8. }

上面这段代码的意思是,只要是代码运行过程中有异常就会进行捕获,并输出出这个异常。然后我们随便编写一个会发生异常的代码,测试出来的异常是这样的。

image.png

这对于前后端分离来说并不好,前后端分离之后唯一的交互就是json了,我们也希望将后端的异常变成json返回给前端处理,所以就需要统一结果返回和统一异常处理。

  1. public class Result<T> {
  2. private Integer code;//状态码
  3. private String message;//提示消息
  4. private T data;//数据
  5. public Result() {
  6. }
  7. /**
  8. * @param code 响应码
  9. * @param message 响应信息
  10. */
  11. public Result(Integer code, String message) {
  12. this.code = code;
  13. this.message = message;
  14. }
  15. /**
  16. * @param code 响应码
  17. * @param message 响应信息
  18. * @param data 数据
  19. */
  20. public Result(Integer code, String message, T data) {
  21. this.code = code;
  22. this.message = message;
  23. this.data = data;
  24. }
  25. /**
  26. * @param resultEnum 自定义枚举类,包含 code 和 message
  27. */
  28. public Result(ResultEnum resultEnum) {
  29. this.code = resultEnum.getCode();
  30. this.message = resultEnum.getMessage();
  31. }
  32. /**
  33. * @param resultEnum 自定义枚举类,包含 code 和 message
  34. * @param data 数据
  35. */
  36. public Result(ResultEnum resultEnum, T data) {
  37. this.code = resultEnum.getCode();
  38. this.message = resultEnum.getMessage();
  39. this.data = data;
  40. }
  41. /**
  42. * 自定义异常返回的结果
  43. * @param definitionException 自定义异常处理类
  44. * @return 返回自定义异常
  45. */
  46. public static Result<Object> defineError(DefinitionException definitionException) {
  47. return new Result<>(definitionException.getErrorCode(), definitionException.getErrorMessage());
  48. }
  49. /**
  50. * 其他异常处理方法返回的结果
  51. * @param resultEnum 自定义枚举类,包含 code 和 message
  52. * @return 返回其他异常
  53. */
  54. public static Result<Object> otherError(ResultEnum resultEnum) {
  55. return new Result<>(resultEnum);
  56. }
  57. //这里写get和set方法
  58. }

注意:其中省略了get,set方法。
ResultEnum:自定义枚举类。

  1. public enum ResultEnum {
  2. // 数据操作定义
  3. SUCCESS(200, "成功"),
  4. TIME_OUT(130, "访问超时"),
  5. NO_PERMISSION(403, "拒绝访问"),
  6. NO_AUTH(401, "未经授权访问"),
  7. NOT_FOUND(404, "无法找到资源"),
  8. METHOD_NOT_ALLOWED(405, "不支持当前请求方法"),
  9. SERVER_ERROR(500, " 服务器运行异常"),
  10. NOT_PARAM(10001, "参数不能为空"),
  11. NOT_EXIST_USER_OR_ERROR_PASSWORD(10002, "该用户不存在或密码错误"),
  12. NOT_PARAM_USER_OR_ERROR_PASSWORD(10003, "用户名或密码为空");;
  13. /**
  14. * 响应码
  15. */
  16. private final Integer code;
  17. /**
  18. * 响应信息
  19. */
  20. private final String message;
  21. /**
  22. * 有参构造
  23. * @param code 响应码
  24. * @param message 响应信息
  25. */
  26. ResultEnum(Integer code, String message) {
  27. this.code = code;
  28. this.message = message;
  29. }
  30. public Integer getCode() {
  31. return code;
  32. }
  33. public String getMessage() {
  34. return message;
  35. }
  36. }

注意:枚举类中定义了常见的错误码以及错误的提示信息。这里我们就定义好了统一的结果返回,其中里面的静态方法是用来当程序异常的时候转换成异常返回规定的格式。
DefinitionException:自定义异常处理类。

  1. //@ControllerAdvice+@ResponseBody,开启全局的异常捕获,返回JSON
  2. @RestControllerAdvice
  3. public class GlobalExceptionHandler {
  4. /**
  5. * 处理自定义异常
  6. * @return Result
  7. * @ExceptionHandler 说明捕获哪些异常,对那些异常进行处理。
  8. */
  9. @ExceptionHandler(value = DefinitionException.class)
  10. public Result<Object> customExceptionHandler(DefinitionException e) {
  11. return Result.defineError(e);
  12. }
  13. /**
  14. * 处理其他异常
  15. * @return Result
  16. */
  17. @ExceptionHandler(value = Exception.class)
  18. public Result<Object> exceptionHandler(Exception e) {
  19. return Result.otherError(ErrorEnum.INTERNAL_SERVER_ERROR);
  20. }
  21. }

说明:将对象解析成json,是为了方便前后端的交互。

三、代码测试与结果

ResultController:测试的controller类

  1. @RestController
  2. public class ResultController {
  3. //获取学生信息
  4. @GetMapping("/student")
  5. public Result<Student> getStudent() {
  6. Student student = new Student();
  7. student.setId(1);
  8. student.setAge(18);
  9. student.setName("XuWwei")
  10. return new Result<>(ResultEnum.SUCCESS, student);
  11. }
  12. //自定义异常处理
  13. @RequestMapping("/getDeException")
  14. public Result<Object> DeException() {
  15. throw new DefinitionException(400, "我出错了");
  16. }
  17. //其他异常处理
  18. @RequestMapping("/getException")
  19. public Result Exception(){
  20. Result result = new Result();
  21. int a=1/0;
  22. return result;
  23. }
  1. @Data
  2. public class Student {
  3. /**
  4. * 唯一标识id
  5. */
  6. private Integer id;
  7. /**
  8. * 姓名
  9. */
  10. private String name;
  11. /**
  12. * 年龄
  13. */
  14. private Integer age;
  15. }

测试结果

启动项目,一个一个测试

  1. 正常测试

image.png
可以看到这个异常被捕获到了,并且返回了一个json。
注意:这种方法是不能处理404异常的,捕获不到。

四、404异常特殊处理

1、修改配置文件

默认情况下,SpringBoot是不会抛出404异常的,所以@ControllerAdvice也不能捕获到404异常。我们可以通过配置文件来让这个注解能捕获到404异常,在application.properties中添加以下配置:

当发现404异常时直接抛出异常 spring.mvc.throw-exception-if-no-handler-found=true 关闭默认的静态资源路径映射,这样404不会跳转到默认的页面 spring.resources.add-mappings=false

但是关闭默认的静态资源路径映射会让静态资源访问出现问题,也就是不适合前后端一体的情况。
但是我们可以手动配置静态资源路径映射,就能正常访问静态资源了。

  1. //不要尝试这么做
  2. @Configuration
  3. public class ResourceConfig implements WebMvcConfigurer {
  4. @Override
  5. public void addResourceHandlers(ResourceHandlerRegistry registry) {
  6. //可以访问localhost:8080/static/images/image.jpg
  7. registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
  8. }
  9. }

2、修改error跳转路径

关闭默认的静态资源路径映射显然不太合理,可能会导致其他的错误发生,所以也可以通过修改默认错误页面的跳转路径来达到我们的目的。
在GlobalExceptionHandler类中添加NotFoundExceptionHandler类,这个类继承了ErrorController,可以重写error的跳转路径。

  1. //处理404NotFoundException
  2. @Controller
  3. class NotFoundExceptionHandler implements ErrorController {
  4. //设置错误页面路径
  5. @Override
  6. public String getErrorPath() {
  7. return "/error";
  8. }
  9. //当访问error路径时,返回一个封装的异常的Json
  10. @RequestMapping("/error")
  11. @ResponseBody
  12. public Result<Object> error() {
  13. return Result.otherError(ResultEnum.NOT_FOUND);
  14. }
  15. }

五、拓展异常类

GlobalExceptionHandler的exceptionHandler方法将所有的异常统一返回500系统错误,这不符合我们的设想,所以我们可以通过判断异常的类型,来返回不同的值。
将exceptionHandler改成以下代码:

  1. /**
  2. * 处理其他异常
  3. * @return Result
  4. */
  5. @ExceptionHandler(value = Exception.class)
  6. public Result<Object> exceptionHandler(Exception e) {
  7. if (e instanceof NullPointerException){
  8. //捕获空指针异常
  9. return Result.otherError(ResultEnum.NOT_PARAM);
  10. }else if (e instanceof IllegalAccessException){
  11. //非法访问异常
  12. return Result.otherError(ResultEnum.NO_PERMISSION);
  13. } else{
  14. return Result.otherError(ResultEnum.SERVER_ERROR);
  15. }
  16. }

注意:更多异常可以通过else if来细分。

六、总结

springboot的异常处理,需要通过@ControllerAdvice注解以及 @ExceptionHandler注解,来拦截所有的异常,并通过一个封装返回值返回。但是,这两个注解无法捕获404NotFound异常,因为SpringBoot默认是不会抛出404异常的,所以要通过继承ErrorController来修改404异常的跳转路径,达到捕获404异常的目的。
image.png