SpringBoot整合全局捕获异常
  Op9yysgqYUmV 2023年11月02日 26 0


异常处理

我们写代码离不开写try-catch语句,在Controller类里面,这也是一种处理异常的方法,但这种方法毕竟有很多弊端,一是我们在每个方法中都写try-catch很麻烦;二是不见得我们的代码能捕获所有异常。事实上SpringBoot 通过 spring-boot-starter-web 启动 WEB 容器的时候,会自动的提供一个映射,URL 是/error,同时会自动加载一个默认的错误处理控制器 - BasicErrorController,这个控制器处理的请求路径是 /error。如果控制器抛出异常,并没有处理的话,都会统一转发到一个 error.html 的错误结果页面,此页面由 SpringBoot(spring-boot-starter-web)提供。其中的处理逻辑是将异常信息分类封装到作用域中,并传递给视图error。

1.1 自定义错误页面

在 SpringBoot 启动的 WEB 应用中,如果一旦发生了异常,且代码中没有对异常做出处理,会自动将请求转发到'/error'路径上,BasicErrorController 会将异常错误信息收集,并通过Model 来传递错误信息,如果系统中没有提供错误页面,则会通过默认的错误页面来显示错误内容,这个默认错误页面是由 SpringBoot 提供的,大致内容如下:

SpringBoot整合全局捕获异常_html

 

如果需要提供一个统一的错误页面处理异常,可以在系统中提供一个 error.html 来实现。

BasicErrorController 收集的错误信息包含:

error - 错误描述,如: Internal Server Error (服务内部错误)

exception - 异常类型, 如: java.lang. ArithmeticException

message - 异常描述, 如: / by zero

timestamp - 时间戳

status - 响应状态码, 如: 200, 500, 404 等。

当系统报错时,返回到页面的内容通常是一些杂乱的代码段,这种显示对用户来说不友好,因此我们需要自定义一个友好的提示系统异常的页面。

在 src/main/resources 下创建 /public/error,在该目录下再创建一个名为 5xx.html 文件,该页面的内容就是当系统报错时返回给用户浏览的内容:

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>系统错误</title>
</head>
<body>
    <div class="container">
        <h2>系统内部错误</h2>
    </div>
</body>
</html>

Spring Boot 会在系统报错时将返回视图指向该目录下的文件。如下图:

SpringBoot整合全局捕获异常_html_02

 

上边处理的 5xx 状态码的问题,接下来解决 404 状态码的问题。

当出现 404 的情况时,用户浏览的页面也不够友好,因此我们也需要自定义一个友好的页面给用户展示。

在 /public/error 目录下再创建一个名为 404.html 的文件:

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>访问异常</title>
</head>
<body>
    <div class="container">
        <h2>找不到页面</h2>
    </div>
</body>
</html>

我们请求一个不存在的资源,如:http://localhost:8080/hi,结果如下图:

SpringBoot整合全局捕获异常_自定义_03

 

全局异常捕获

虽然通过定义页面,我们可以对错误进行展示,但很明显,所有的错误都是相同的展示,在实际生产中是达不到我们的业务需要的。

1.2@ExceptionHandler

Spring 支持异常的细致化处理,可以在控制器中定义若干方法,专门用于处理异常。处理异常的方法使用注解@ExceptionHandler 来描述,注解的 value 属性是 Class[]类型,代表该方法可以处理的异常种类。我们写个简单的例子来说明下:

@RestController
@RequestMapping("/handler")
public class HandlerController {
    @RequestMapping("/test")
    public void test() {
        throw new NullPointerException("出错了!");
    }

    @ExceptionHandler({NullPointerException.class})
    public String exception(NullPointerException e) {
        System.out.println(e.getMessage());
        e.printStackTrace();
        return "null pointer exception";
    }
}

这种异常处理方式力度较细,处理方式多样化。其缺陷是,如果定义了太多的异常处理方法,会提升维护成本;且异常处理方法只对当前控制器有效。代码有 bad smell。

这种开发方式适合针对性处理。 因为定义在控制器中的异常处理方法处理优先级最高 。

1.3@ControllerAdvice&@ExceptionHandler

Spring 支持控制器 Advice 定义。原理是使用AOP对Controller控制器进行增强(前置增强、后置增强、环绕增强,AOP原理请自行查阅);可以独立定义一个类型作为 ControllerAdvice,类型需要使用 @ControllerAdvice来描述,类型中定义若干异常处理方法,方法使用@ExceptionHandler 来描述。

当应用中发生异常时,异常的处理顺序是: 当前控制器中定义的@ExceptionHandler方法 -> @ControllerAdvice 类中定义的@ExceptionHandler 方法 -> BasicErrorController 中定义的服务方法(error.html 默认异常页面)

@ControllerAdvice
public class GlobalExceptionHandler {
    /**
     * 自定义errors.html,返回错误信息
     * @param request
     * @param response
     * @param e
     * @return
     * @throws Exception
     */
    @ExceptionHandler(value = Exception.class)
    public Object errorHandler(HttpServletRequest request,
                               HttpServletResponse response, Exception e)  {
        e.printStackTrace();
        ModelAndView mav = new ModelAndView();
        mav.addObject("exception", e);
        mav.addObject("url", request.getRequestURL());
        mav.setViewName("my_error");
        return mav;
    }
}
@Controller
@RequestMapping("/advice")
public class AdviceController {
    @RequestMapping("/hello")
    public ModelAndView hello() {
        ModelAndView mav = new ModelAndView("index");
        mav.addObject("msg", "hello");
        int result=5/0;
        return mav;
    }
}

使用 @ControllerAdvice + @ExceptionHandler 进行全局的 Controller 层异常处理,只要设计得当,就再也不用在 Controller 层进行 try-catch 了!而且,@Validated 校验器注解的异常,也可以一起处理,无需手动判断绑定校验结果 BindingResult/Errors 了!

优缺点

  • 优点:将 Controller 层的异常和数据校验的异常进行统一处理,减少模板代码,减少编码量,提升扩展性和可维护性。
  • 缺点:只能处理 Controller 层未捕获(往外抛)的异常,对于 Interceptor(拦截器)层的异常,Spring 框架层的异常,就无能为力了。

1.4HandlerExceptionResolver

Spring支持使用@Configuration的配置方式,为控制器配置一个HandlerExceptionResolver 来处理异常。

HandlerExceptionResolver 是一个接口,这个接口有若干实现类,常用实现类是

SimpleMappingExceptionResolver。这个类型通过一个 Properties 对象来配置异常种类和错误页面的映射关系,并通过 Model 向客户端传递异常对象,attribute 名称为 exception。在视图逻辑中,可以通过异常对象来获取需要使用的异常信息。

当应用中发生异常时,异常的处理顺序是: 当前控制器中定义的@ExceptionHandler

方法 -> @ControllerAdvice 类中定义的@ExceptionHandler 方法 -> @Configuration 类中配置

的 HandlerExceptionResolver -> BasicErrorController 中定义的服务方法(error.html 默认异常

页面)

优点: 使用一个 Configuration 来定义若干的错误处理映射,开发效率更高。

缺点: 优先级太低。会被大多数的异常处理逻辑覆盖。

1.5 自定义 HandlerExceptionResolver

HandlerExceptionResolver 也可以自定义提供实现。只需要为其定义的抽象方法提供实现即可:

/**
* 处理异常逻辑,在方法中,只需要对参数 ex 进行逻辑控制,并提供相应的返回结果,
* 即可实现异常逻辑的自定义处理。
* @param request - 请求对象
* @param response - 应答对象
* @param handler - 发生异常的控制器对象
* @param ex - 发生的异常对象
* @return - 返回的数据模型和视图
*/
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object handler,Exception ex) {
return null;
}

我们写个例子说明

@Component
public class CustomExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest,
                                         HttpServletResponse httpServletResponse,
                                         Object o, Exception e) {
        String ex=null;
        if( e instanceof Exception){
            ex=e.getMessage();
        }else{
            ex="未知错误";
        }

        ModelAndView mv=new ModelAndView();
        mv.addObject("exception", e);
        mv.addObject("url", httpServletRequest.getRequestURL());
        mv.setViewName("my_error");
        return mv;
    }
}

至于controller类就不再写了,想看的话可以看下我的代码

 

 

参考:


https://www.jianshu.com/p/d44dc345bb10


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
Op9yysgqYUmV