3.9 统一异常处理

125 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第17天,点击查看活动详情

3.9 统一异常处理

SpringBoot中异常的自动处理

浏览器发送请求的时候一律发送给表现层,然后 表现层 调用 业务层,业务层 调用 数据层,假如 数据层 出异常了,它会抛给 业务层,然后 业务层 会抛给 表现层。所以整个三层架构,无论 数据层还是业务层哪个层次的异常,最终都会汇集到表现层,因此只要我们统一的对表现层捕获异常就能处理系统的所有异常

SpringBoot中异常处理

SpringBoot会自动处理异常,跳转到相应的 404.html 、500.html 对应的错误状态页面。

我们先将 404.html、500.html页面处理一下:

文件夹名字一定要叫 error,且在 templates 目录下,错误文件的名字一定要叫错误状态。

image-20220719085500452

image-20220719085644094

image-20220719090041068

运行项目测试:

image-20220719090059262

image-20220719090137416

SpringBoot会自动给异常进行一个处理,但是这个处理还不是一定符合我们的预期,

比如 404 这个就ok,找不到资源没有更好的办法,500 就不一样,500 意味着服务端报错了,报错的时候我们最好记个日志,好以后分析,所以直接跳转页面这只是表面上的一个处理,我们内在需要记个日志这个还没有处理。

SpringBoot中异常的详细处理

接下来我们来看一下如何统一记录日志:

image-20220719081530683

  • @ModelAttribute

绑定数据的意思是:

比如 controller 有很多请求,这些请求当中要用到同一个数据给模板,我们需要往 model

里装同一个数据,我们可以利用 @ModelAttribute 加一个方法给 model 统一绑定参数,给所有的 controller 用。

  • @DataBinder

    这个注解的作用是,页面向服务器传参,会被自动的做转换,是因为内部调了很多的参数转换器,万一程序当中默认的参数转换器不够用,有一个特殊的类型需要处理,可以自定义一个转换器,然后用 @DataBinder 把它注册上。

上面的 @ModelAttribute 和 @DataBinder 目前没有需求要用,所以就不演示了。接下来我们演示利用 @ExceptionHandler 统一处理所有 controller 可能发生的异常。

演示

首先在 HomeController 里加上一个请求,为什么呢?因为controller发生异常之后,我们统一处理,记了日志,处理完以后我们得去到 500那个页面,这个时候因为是我们人为处理的,我们需要手动的重定向过去,所以我们需要提前把 500 页面这个请求的访问给它配一下,增加一下这个请求的处理。

@RequestMapping(path = "/error", method = RequestMethod.GET)
public String getErrorPage() {
    return "/error/500";
}

image-20220719101404514

接下来我们需要用@ControllerAdvice 声明一个 controller 全局配置类,然后对所有 controller 的异常做统一的处理

@ControllerAdvice 不加参数的话会扫描所有的bean,范围太大了,我们通常需要一个限制,让它范围小一点,

@ControllerAdvice(annotations = Controller.class) 的意思是 这个注解只扫描带有 @Controller 注解的那些组件,

我们需要在这个类里面加一个方法处理所有的错误的情况,方法之前加一个注解@ExceptionHandler 表示这个方法是处理所有异常的方法

@ControllerAdvice(annotations = Controller.class)
public class ExceptionAdvice {

    // 为了记日志
    private static Logger logger = LoggerFactory.getLogger(ExceptionAdvice.class);

    @ExceptionHandler({Exception.class})   // 里面的参数表示处理所有异常
    public void handlerException(Exception e, HttpServletRequest request, HttpServletResponse response) throws IOException {        // 方法名无所谓
        // 记录一下异常的概括
        logger.error("服务器发生异常: " + e.getMessage());
        // 详细记录异常的栈的信息
        for (StackTraceElement element : e.getStackTrace()) {
            logger.error(element.toString());
        }
        /*
            普通的请求重定向到刚刚500的controller
            异步请求的话返回json,json字符串里面写错误信息
         */
        // 判断请求是普通请求还是异步请求
        String xRequestedWith = request.getHeader("x-requested-with");
        if ("XMLHttpRequest".equals(xRequestedWith)) {
            // 异步请求
            response.setContentType("application/plain;charset=utf-8"); // plain表示向浏览器返回的是一个普通的字符串
            PrintWriter writer = response.getWriter();
            writer.write(CommunityUtil.getJSONString(1, "服务器异常!"));
        } else {
            // 普通请求重定向到错误页面
            response.sendRedirect(request.getContextPath() + "/error");
        }
    }
}

image-20220719093924630

image-20220719101522812

上面的处理的话如果是 500 错误,表示服务器错误,会记个日志,然后跳到 500 那个controller,然后这个 controller会跳转到 500.html 界面

如果是 404 错误,也就是找不到资源,会直接跳转到 404.html 页面

测试

image-20220719101736530

普通请求的 500 异常

image-20220719101914295

异步发私信的 500 异常

image-20220719101759548

到这里,统一异常处理就完成了。