特厉害的一个功能,全局异常处理『SpringMVC系列』

0、目录

  • 1、本文内容

  • 2、通常我们是如何处理异常的?

  • 3、上面代码存在的问题

  • 4、如何更好的解决这个问题?

  • 5、springmvc 集中统一处理异常的使用步骤

    • 5.1、需求

    • 5.2、具体实现步骤:3 个步骤

    • 5.3、验证效果(4 种场景)

  • 6、总结

  • 7、案例代码

  • 8、SpringMVC 系列

  • 9、更多好文章

  • 10、领取后端必备的 200 本书

文末可领取最近刚整理的,后端必备的 200 本书籍。

1、本文内容

带大家掌握 springmvc 中统一异常处理的使用。

2、通常我们是如何处理异常的?

看一下下面的代码,每个方法中有一段 try catch,用来对业务异常进行处理,不知道大家是否写过这种类似的代码,这种代码有什么问题么?

public class Demo {
    public void m1() {
        try {
            //业务代码
        } catch (Exception e) {

        }
    }

    public void m2() {
        try {
            //业务代码
        } catch (Exception e) {

        }
    }

    ....
}

3、上面代码存在的问题

先来思考一个问题,当系统发生异常了,我们会怎么做?

为了方便排查错误,我们会在 catch 中将异常信息记录到日志文件中,变成了下面这样

public class Demo {
    public void m1() {
        try {
            //业务代码
        } catch (Exception e) {
            //1、将错误信息记录到日志文件
        }
    }

    public void m2() {
        try {
            //业务代码
        } catch (Exception e) {
            //1、将错误信息记录到日志文件
        }
    }

    ....
}

如果发生了严重的异常,我们需要第一时间让开发人员介入,怎么办呢?

我们可以在 catch 中可以发送短信给开发者,让其第一时间介入解决。

此时是不是又要改代码,catch 又要添加代码了,此时要改很多很多代码,得重新测试一遍了,此时对于开发和测试都是一种灾难。

4、如何更好的解决这个问题?

采用 aop 的方式,将异常处理和业务代码进行分离,让框架拦截所有方法的执行,目标方法中不要在捕获异常了,直接将异常抛出去,由统一的地方进行进行处理。

此时异常处理和业务代码分离开了,没有耦合在一起了,此时如果需要调整异常的处理逻辑,会非常方便,只需要修改统一处理异常的代码,就 ok 了。

如果对 spring 的 aop 比较熟悉的,实现起来还是很容易的,只需要一个环绕拦截器就可以了,有兴趣的朋友可以去试试。

本文是 springmvc 的内容,而 springmvc 中提供了类似的方式来统一处理系统所有的异常,Controller 中的所有方法都无需捕获异常,只需要做一些配置,springmvc 框架就会自动捕获异常,对异常进行集中处理,下面咱们来看看这玩意是怎么玩的。

5、springmvc 集中统一处理异常的使用步骤

下面通过一个案例来看一下具体如何使用的。

5.1、需求

写个登录方法,方法中验证用户和密码,验证失败的时候分别抛出对应的异常,成功了跳转到 success.jsp 页面,代码如下。

@Controller
public class UserController {

    @RequestMapping("/login")
    public ModelAndView login(@RequestParam("name") String name,
                              @RequestParam("pass") Integer pass) throws Exception {
        //用户名必须为路人
        if (!"路人".equals(name)) {
            throw new NameException("用户名有误!");
        }
        //密码必须为666
        if (Integer.valueOf(666).equals(pass)) {
            throw new PassException("密码有误!");
        }
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("name", name);
        modelAndView.setViewName("success");
        return modelAndView;
    }
}

/**
 * 姓名异常类
 */
public class NameException extends Exception {
    public NameException(String message) {
        super(message);
    }
}

/**
 * 密码异常类
 */
public class PassException extends Exception {
    public PassException(String message) {
        super(message);
    }
}

需要实现的需求

从代码中来看,我们可以将异常分为 3 类:NameException、PassException、其他异常,咱们需要实现让 springmvc 来统一处理这 3 类异常,然后将错误信息输出到错误页面。

5.2、具体实现步骤:3 个步骤

step1:创建全局异常处理类(非常关键)

这个步骤是重点,内部包含 3 个小的步骤。

第 1 步:创建一个普通的类,作为全局异常处理类

第 2 步:在类上添加@ControllerAdvice 注解,从注解的名称包含了 Advice 可以看出,aop 中我们接触过 Advice(通知),用来对 bean 的功能进行增强,而这个注解是对 Controller 的功能进行增强,用来集中处理 Controller 的所有异常。

第 3 步:添加处理异常的方法,方法上需要加上@ExceptionHandler 注解,这个注解有个 value 属性,用来指定匹配的异常类型,当 springmvc 捕获到控制器异常后,会和这个异常类型进行匹配,匹配成功了,将调用@ExceptionHandler 标注的方法;如果未指定 value 的值,表示匹配所有类型的异常。

最终代码如下,类中添加了 3 个方法,分别用来处理 3 类异常,方法的每一行输出了一条日志,稍后可以通过这个验证效果,方法内部对错误进行了封装,然后跳转到错误页面(error.jsp)进行展示,稍后我们会通过不同的场景来验证效果。

package com.javacode2018.springmvc.chat10.handle;

import com.javacode2018.springmvc.chat10.exception.NameException;
import com.javacode2018.springmvc.chat10.exception.PassException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;

/**
 * 统一异常处理类
 */
@ControllerAdvice
public class GlobalExceptionHandle {

    /**
     * 此方法用来处理 NameException 类型的异常,
     * 当controller抛出NameException异常的时候,此方法会被调用
     *
     * @param e
     * @return
     */
    @ExceptionHandler({NameException.class})
    public ModelAndView doNameException(Exception e) {
        System.out.println("-----doNameException-----");
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("error");
        modelAndView.addObject("msg", "登录名有误!");
        modelAndView.addObject("e", e);
        return modelAndView;
    }

    /**
     * 此方法用来处理 AgeException 类型的异常,
     * 当controller抛出NameException异常的时候,此方法会被调用
     *
     * @param e
     * @return
     */
    @ExceptionHandler({PassException.class})
    public ModelAndView doPassException(Exception e) {
        System.out.println("-----doPassException-----");
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("error");
        modelAndView.addObject("msg", "密码有误!");
        modelAndView.addObject("e", e);
        return modelAndView;
    }

    /**
     * 此方法用来处理任意异常(也就是上面2个方法不能够处理的异常都会被这个方法处理)
     *
     * @param e
     * @return
     */
    @ExceptionHandler
    public ModelAndView doException(Exception e) {
        System.out.println("-----doException-----");
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("error");
        modelAndView.addObject("msg", "系统异常!");
        modelAndView.addObject("e", e);
        return modelAndView;
    }
}
step2:创建 springmvc 配置文件

添加如下配置

完整配置如下

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd   http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 设置controller扫描路径 -->
    <context:component-scan base-package="com.javacode2018.springmvc.chat10.controller"/>
    <!-- 设置全局异常处理器扫描路径 -->
    <context:component-scan base-package="com.javacode2018.springmvc.chat10.handle"/>
    <!-- 添加mvc注解驱动 -->
    <mvc:annotation-driven/>

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/view/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

</beans>
step3:创建几个页面
登录页面(index.jsp)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head></head>
<body>
<div>
    <form method="post" action="${pageContext.request.contextPath}/login">
        姓名:<input name="name"/><br/>
        密码:<input name="pass"/><br/>
        <input type="submit" value="登录">
    </form>
</div>
</body>
</html>
登录成功页面(/WEB-INF/view/success.jsp)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h1>欢迎您,登录成功!</h1><br/>
当前用户:${requestScope.name}<br/>
</body>
</html>
错误页面(error.jsp)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h1>登录失败!</h1><br/>
错误提示:${requestScope.msg}<br/>
Exception.message:${e.message}
</body>
</html>

5.3、验证效果(4 种场景)

下面我们来分别验证正常情况、用户名错误的情况、密码错误的情况、密码为非数字的情况,然后注意 idea 控制台和浏览器的输出效果。

场景 1:正常情况

场景 2:姓名错误的情况

控制台输出如下,说明进到了GlobalExceptionHandle#doNameException方法了

-----doNameException-----

页面输出

场景 3:密码错误的情况

控制台输出如下,说明进到了GlobalExceptionHandle#doPassException方法了

-----doPassException-----

页面输出

场景 4:密码为非数字的情况

控制台输出如下,说明进到了GlobalExceptionHandle#doException方法了

-----doException-----

页面输出如下,由于密码必须为数字,而我们输入的是abc,无法转换为数字,此时 springmvc 内部在进行参数转换的时候,会自动抛出异常,从下面的异常信息中也可以看出是类型转换错误

6、总结

  • 本文详细介绍了 springmvc 集中统一异常处理的具体用法,大家消化一下,重点主要用到了 2 个注解@ControllerAdvice@ExceptionHandler

  • 目前多数系统都是前后端分离了,后端所有的接口都返回 json 格式的数据,所以下一篇文章,来一篇实战的文章,带大家看下在 controller 这层,如何实现通用的一些设计,主要包含了通用返回值及统一异常处理的设计。

7、案例代码

git地址:https://gitee.com/javacode2018/springmvc-series

8、SpringMVC 系列

  1. SpringMVC 系列第 1 篇:helloword

  2. SpringMVC 系列第 2 篇:@Controller、@RequestMapping

  3. SpringMVC 系列第 3 篇:异常高效的一款接口测试利器

  4. SpringMVC 系列第 4 篇:controller 常见的接收参数的方式

  5. SpringMVC 系列第 5 篇:@RequestBody 大解密,说点你不知道的

  6. SpringMVC 系列第 6 篇:上传文件的 4 种方式,你都会么?

  7. SpringMVC 系列第 7 篇:SpringMVC 返回视图常见的 5 种方式,你会几种?

  8. SpringMVC 系列第 8 篇:返回 json & 通用返回值设计

  9. SpringMVC 系列第 9 篇:SpringMVC 返回 null 是什么意思?

  10. SpringMVC 系列第 10 篇:异步处理

  11. SpringMVC 系列第 11 篇:集成静态资源

  12. SpringMVC 系列第 12 篇:拦截器

9、更多好文章

  1. Spring 高手系列(共 56 篇)

  2. Java 高并发系列(共 34 篇)

  3. MySql 高手系列(共 27 篇)

  4. Maven 高手系列(共 10 篇)

  5. Mybatis 系列(共 12 篇)

  6. 聊聊 db 和缓存一致性常见的实现方式

  7. 接口幂等性这么重要,它是什么?怎么实现?

  8. 泛型,有点难度,会让很多人懵逼,那是因为你没有看这篇文章!

10、领取后端必备的 200 本书

这里帮大家整理 200 本后端必备的书籍,扫描下面二维码即可看到书籍列表,直接领取电子版,也可以点击【阅读原文】领取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

路人甲Java

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值