SpringMVC Day 09 : 异常处理

前言

在Web应用程序开发中,我们经常会遇到各种各样的错误和异常情况。那么如何有效地捕获和处理这些异常呢?本文将介绍Spring MVC中的异常处理机制,帮助您构建更稳定、可靠的Web应用程序。

一、前期准备

1、新建项目,结构如下
2、导入依赖
XML 复制代码
    <dependencies>
    
        <!-- springmvc 依赖,会将spring的核心包一并添加进来 -->
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.23</version>
        </dependency>
     
 
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version>
        </dependency>
 
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.4.5</version>
        </dependency>
 
 
 
    </dependencies>
3、配置 web.xml
XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>


</web-app>

用于配置 Servlet 的映射和加载。在 Spring MVC 中,它用于配置 DispatcherServlet 的初始化和请求映射。

具体来说,这段配置的作用如下:

  1. 定义了一个名为 "dispatcher" 的 Servlet,并指定了 org.springframework.web.servlet.DispatcherServlet 作为其处理类。
  2. 设置了 load-on-startup 属性为 1,表示在应用启动时就加载该 Servlet。
  3. 使用 <servlet-mapping> 元素将 "dispatcher" Servlet 映射到所有的请求路径上(即 <url-pattern>/</url-pattern>),意味着所有的请求都会经过该 Servlet 进行处理。

这段配置的作用是将所有的请求交给 DispatcherServlet 处理,并让它成为应用的核心控制器。DispatcherServlet 将根据请求的 URL 和其他配置信息,将请求分发给相应的处理器方法进行处理,然后返回响应结果。

4、新建一个 User 类
java 复制代码
@Data
public class User {

    private String userName;
    private String password;

}
5、新建一个 UserDao 接口
java 复制代码
public interface UserDao {


    User getUser(String userName);

}
6、新建一个 UserDaoImpl 实现类
java 复制代码
@Repository
@Slf4j
public class UserDaoImpl implements UserDao {
    @Override
    public User getUser(String userName) {
        log.info("select * from user ");
        User user = new User();
        user.setUserName("qiu");
        user.setPassword("88888888");
        return user;
    }
}

使用日志简单的输出,并封装数据到 user 实体中。

7、新建一个 LoginService
java 复制代码
public interface LoginService {

    /**
     * 验证用户
     * @param userName
     * @param password
     * @return
     */
    User auth(String userName,String password);

}

二、传统的 try catch 处理异常

1、编写 controller 类
java 复制代码
@RestController
@RequiredArgsConstructor
@Slf4j
public class UserController {

    private final LoginService loginService;


    @PostMapping("/login")
    public ResultVO login(String userName, String password, HttpSession session){

        try{
            User user = loginService.auth(userName, password);
            // 将 user 保存到会话中
            user.setUserName(userName);
            user.setPassword(password);
            session.setAttribute("user",user);

            return new ResultVO();
        } catch (AuthException e){
            // 验证未通过则创建提示信息
            ResultVO resultVO = new ResultVO();
            resultVO.setCode(e.getErroeCode());
            resultVO.setMessage(e.getMessage());
            return resultVO;
            // 捕获其他非业务异常(也就是服务器内部错误异常)
        } catch (RuntimeException e){
            // 记录异常日志
            log.error(e.getMessage());
            // 验证未通过则创建提示信息
            ResultVO resultVO = new ResultVO();
            resultVO.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
            resultVO.setMessage("服务器内部错误,请稍候重试");
            return resultVO;
        }

    }
}

我们使用之前的方法是不是需要有很多个 catch 语句块去抛出多个异常,这还是异常少的例子,如果还有更多的异常,还要很多的 catch 语句块,这样就会显得我们的 Controler 很臃肿。我们的控制器不能有这么多的多余的代码,那么有没有办法把异常信息抽取出来呢?

三、局部异常

1、自定义一个异常类来处理业务类的异常(登录)
java 复制代码
/**
 * @Date 2023-10-27
 * @Author qiu
 * 自定义异常
 */
public class AuthException extends RuntimeException{

    /**
     * 异常状态码
     */
    private Integer erroeCode;

    public AuthException(Integer erroeCode,String message){
        super(message);
        this.erroeCode =erroeCode;
    }

    public Integer getErroeCode() {
        return erroeCode;
    }
}

这是一个自定义异常类AuthException的示例代码。

该异常类继承自RuntimeException,因此是一个运行时异常。它的目的是在程序中处理身份验证相关的异常情况。

在该类中,有两个成员变量:

  • erroeCode:表示异常状态码,用于标识不同的异常情况。
  • message:异常的详细信息。

构造方法AuthException(Integer erroeCode, String message)用于创建AuthException对象,并接受一个异常状态码和异常信息作为参数。它调用父类的构造方法super(message)来设置异常信息,并将异常状态码赋值给成员变量erroeCode

同时,类中提供了getErroeCode()方法用于获取异常状态码。

通过自定义异常类,可以在程序中抛出和捕获AuthException对象,以便对身份验证过程中的异常进行更细粒度的处理和控制。

2、完成 LoginImpl 实现类
java 复制代码
@Service
@Slf4j
@RequiredArgsConstructor
public class LoginServiceImpl implements LoginService {

    private final UserDao userDao;

    @Override
    public User auth(String userName, String password) {

        User user = userDao.getUser(userName);
        // 用户不为 Null 则校验密码
        if ( user != null ){
            if ( password.equals(user.getPassword())){
                return user;
            }
        }
        // 抛出业务异常
        throw new AuthException(10001,"账号密码错误");
    }

}

在类中,定义了一个userDao成员变量,并通过构造方法注入UserDao对象。

实现了LoginService接口中的auth()方法,用于进行用户名和密码的校验。首先通过userDao.getUser(userName)方法获取对应用户名的用户对象,然后判断用户是否存在以及密码是否匹配。如果匹配成功,则返回用户对象;否则,抛出一个业务异常AuthException,并设置异常状态码为10001,异常信息为"账号密码错误"。

通过LoginServiceImpl类,可以实现用户登录认证的功能,并且在校验失败时抛出自定义的业务异常,以便后续处理和提供详细的错误提示。

3、使用 @ExceptionHandler 注解
java 复制代码
    /**
     * 局部异常处理,处理登录业务异常
     * @ExceptionHandler:注解标注的方法专门用于处理请求方式产生的异常。
     * value:属性指定要处理的异常类型
     * 注意:这个局部异常的范围只在当前的 Controller 中有效,也就是说
     * 每个 controller 都有自己专门的 handlerException
     * @param e
     * @return
     */
    @ExceptionHandler(AuthException.class)
    public ResultVO handlerAuthException(AuthException e){
        // 验证未通过则创建提示信息
        ResultVO resultVO = new ResultVO();
        resultVO.setCode(e.getErroeCode());
        resultVO.setMessage(e.getMessage());
        return resultVO;
    }

这是一个局部异常处理的示例代码,用于处理登录业务异常。

在该代码中,使用@ExceptionHandler注解标记了一个方法,该方法专门用于处理AuthException类型的异常。value属性指定要处理的异常类型,即AuthException.class

当控制器(Controller)中的请求处理方法抛出AuthException异常时,Spring框架会自动调用该方法进行异常处理。在该方法中,首先创建一个ResultVO对象作为返回结果,并将异常状态码和异常信息设置到ResultVO对象中。然后将ResultVO对象作为方法的返回值返回。最终,将返回结果以JSON格式返回给客户端。

通过局部异常处理,可以提高代码的可读性和可维护性,将异常处理逻辑从业务逻辑中分离出来,并且能够对不同类型的异常做出不同的响应。

四、全局异常

1、新建 ExceptionAdvice 类
java 复制代码
/**
 * @Date 2023-10-27
 * @Author qiu
 * 定义一个全局异常处理类(类似一个切面)
 * 这个类中定义所有的异常处理方法,也可以
 * 理解为全局异常通知
 */
//@ControllerAdvice (对应 @Controller 注解的类)

// (对应 @RestController 注解的类)
// value 屬性 controller 包下所有类都需要捕获异常
@RestControllerAdvice("edu.nf.ch09.controller")

@Slf4j
public class ExceptionAdvice {

    /**
     * 全局异常处理,处理登录业务异常
     * @ExceptionHandler:注解标注的方法专门用于处理请求方式产生的异常。
     * value:属性指定要处理的异常类型
     * 注意:这个局部异常的范围只在当前的 Controller 中有效,也就是说
     * 每个 controller 都有自己专门的 handlerException
     * @param e
     * @return
     */
    @ExceptionHandler(AuthException.class)
    public ResultVO handlerAuthException(AuthException e){
        // 验证未通过则创建提示信息
        ResultVO resultVO = new ResultVO();
        resultVO.setCode(e.getErroeCode());
        resultVO.setMessage(e.getMessage());
        return resultVO;
    }


    /**
     * 全局的异常处理(处理非业务异常,如:数据库异常)
     * @return
     */
    @ExceptionHandler(RuntimeException.class)
    public ResultVO handlerRunTimeException(RuntimeException e ){
        // 捕获其他非业务异常(也就是服务器内部错误异常)
        // 记录异常日志
        log.error(e.getMessage());
        // 验证未通过则创建提示信息
        ResultVO resultVO = new ResultVO();
        resultVO.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
        resultVO.setMessage("服务器内部错误,请稍候重试");
        return resultVO;
    }

}

这是一个全局异常处理类,用于统一处理控制器中抛出的异常。

在该代码中,使用@RestControllerAdvice注解标记了一个类,该类是一个切面,用于处理全局异常。通过value属性指定了需要捕获异常的控制器层(Controller)所在的包路径。

在该类中,定义了两个异常处理方法。

  1. handlerAuthException方法用于处理AuthException类型的异常。当控制器中的请求处理方法抛出AuthException异常时,Spring框架会自动调用该方法进行异常处理。在该方法中,首先创建一个ResultVO对象作为返回结果,并将异常状态码和异常信息设置到ResultVO对象中。然后将ResultVO对象作为方法的返回值返回。

  2. handlerRunTimeException方法用于处理RuntimeException类型的异常(即非业务异常,如数据库异常等)。当控制器中的请求处理方法抛出RuntimeException异常时,Spring框架会自动调用该方法进行异常处理。在该方法中,首先记录异常日志,然后创建一个ResultVO对象作为返回结果,并设置一个服务器内部错误的提示信息和状态码。最终将ResultVO对象作为方法的返回值返回。

通过全局异常处理类,可以集中处理控制器中抛出的异常,并返回统一的响应结果,提高代码的可读性和可维护性。

我们把异常都放在这个类来处理,就不用在 controller 写那么多处理异常代码了。

2、编写 controller 类
java 复制代码
@RestController
@RequiredArgsConstructor
@Slf4j
public class UserController {

    private final LoginService loginService;

    @PostMapping("/login")
    public ResultVO login(String userName, String password, HttpSession session){

        User user = loginService.auth(userName, password);
        // 将 user 保存到会话中
        user.setUserName(userName);
        user.setPassword(password);
        session.setAttribute("user",user);

        return new ResultVO();

    }
}

在控制器中只需要完成对业务类的方法调用即可,不用去考虑异常怎么处理。

3、运行效果

五、总结

本次案例,介绍了三种处理异常的方式,我们学习了 SpringMVC 后就要使用全局的处理异常。比起原始的 try catch 语句块处理异常是不是简洁很多了,我们把同样的事情单独放在一个类中去完成,就不会像刚刚那样把所有代码都写在控制器中那么臃肿了。

六、使用 Springmvc 异常处理的好处

使用Spring MVC异常处理的好处有以下几个方面:

  1. 统一异常处理:通过在全局配置中定义统一的异常处理类,可以集中处理应用程序中所有控制器抛出的异常。这样可以减少代码重复,提高代码的可维护性和可读性。

  2. 简化异常处理逻辑:在应用程序的控制器中,通常需要编写大量的异常处理代码来处理各种可能发生的异常情况。使用Spring MVC的异常处理功能,可以将这些异常处理逻辑从控制器中抽离出来,使得控制器代码更加简洁和清晰。

  3. 统一错误信息:通过自定义异常处理类,可以将不同类型的异常映射到统一的错误信息,并返回给客户端。这样可以提供一致的错误信息格式,便于前端进行处理和展示。

  4. 异常日志记录:在异常处理类中,可以对异常进行日志记录,包括异常的详细信息、发生异常的时间等。这样可以帮助开发人员快速定位和解决问题,提高系统的可靠性和稳定性。

  5. 提升用户体验:通过合理地处理异常,可以向用户提供更友好的错误提示信息,避免出现系统默认的错误页面或者错误信息。这样可以提升用户体验,增加系统的可用性。

总之,使用Spring MVC的异常处理功能可以简化开发过程,提高代码的可维护性和可读性,提供统一的错误信息和日志记录,并提升用户体验。这些优势使得Spring MVC成为开发Web应用程序时的首选框架之一。

七、gitee 案例

地址:ch09 · qiuqiu/SpringMVC - 码云 - 开源中国 (gitee.com)

相关推荐
虫小宝9 分钟前
如何在Java中实现PDF生成
java·开发语言·pdf
菜鸡且互啄691 小时前
在线教育平台,easyexcel使用案例
java·开发语言
八月林城1 小时前
JAVA导出数据库字典到Excel
java·数据库·excel
浅念同学3 小时前
算法-常见数据结构设计
java·数据结构·算法
杰哥在此5 小时前
Java面试题:讨论持续集成/持续部署的重要性,并描述如何在项目中实施CI/CD流程
java·开发语言·python·面试·编程
咖啡煮码6 小时前
深入剖析Tomcat(十五、十六) 关闭钩子,保证Tomcat的正常关闭
java·tomcat
C.C6 小时前
java IO流(1)
java·开发语言
黑头!7 小时前
Tomcat注册为服务之后 运行时提示JVM异常
java·jvm·tomcat
袁震7 小时前
Java---Mybatis详解二
java·开发语言·mybatis
《黑巧克力》8 小时前
【JavaEE】多线程进阶
java·spring·java-ee·maven·dubbo·idea