在Web应用程序开发中,我们经常会遇到各种各样的错误和异常情况。那么如何有效地捕获和处理这些异常呢?本文将介绍Spring MVC中的异常处理机制,帮助您构建更稳定、可靠的Web应用程序。
org.springframework
spring-webmvc
5.3.23
org.projectlombok
lombok
1.18.30
ch.qos.logback
logback-classic
1.4.5
dispatcher
org.springframework.web.servlet.DispatcherServlet
1
dispatcher
/
用于配置 Servlet 的映射和加载。在 Spring MVC 中,它用于配置 DispatcherServlet 的初始化和请求映射。
具体来说,这段配置的作用如下:
- 定义了一个名为 "dispatcher" 的 Servlet,并指定了 org.springframework.web.servlet.DispatcherServlet 作为其处理类。
- 设置了 load-on-startup 属性为 1,表示在应用启动时就加载该 Servlet。
- 使用
元素将 "dispatcher" Servlet 映射到所有的请求路径上(即 / ),意味着所有的请求都会经过该 Servlet 进行处理。这段配置的作用是将所有的请求交给 DispatcherServlet 处理,并让它成为应用的核心控制器。DispatcherServlet 将根据请求的 URL 和其他配置信息,将请求分发给相应的处理器方法进行处理,然后返回响应结果。
@Data
public class User {
private String userName;
private String password;
}
public interface UserDao {
User getUser(String userName);
}
@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 实体中。
public interface LoginService {
/**
* 验证用户
* @param userName
* @param password
* @return
*/
User auth(String userName,String password);
}
@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 很臃肿。我们的控制器不能有这么多的多余的代码,那么有没有办法把异常信息抽取出来呢?
/**
* @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
对象,以便对身份验证过程中的异常进行更细粒度的处理和控制。
@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
类,可以实现用户登录认证的功能,并且在校验失败时抛出自定义的业务异常,以便后续处理和提供详细的错误提示。
/**
* 局部异常处理,处理登录业务异常
* @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格式返回给客户端。通过局部异常处理,可以提高代码的可读性和可维护性,将异常处理逻辑从业务逻辑中分离出来,并且能够对不同类型的异常做出不同的响应。
/**
* @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)所在的包路径。在该类中,定义了两个异常处理方法。
handlerAuthException
方法用于处理AuthException
类型的异常。当控制器中的请求处理方法抛出AuthException
异常时,Spring框架会自动调用该方法进行异常处理。在该方法中,首先创建一个ResultVO
对象作为返回结果,并将异常状态码和异常信息设置到ResultVO
对象中。然后将ResultVO
对象作为方法的返回值返回。
handlerRunTimeException
方法用于处理RuntimeException
类型的异常(即非业务异常,如数据库异常等)。当控制器中的请求处理方法抛出RuntimeException
异常时,Spring框架会自动调用该方法进行异常处理。在该方法中,首先记录异常日志,然后创建一个ResultVO
对象作为返回结果,并设置一个服务器内部错误的提示信息和状态码。最终将ResultVO
对象作为方法的返回值返回。通过全局异常处理类,可以集中处理控制器中抛出的异常,并返回统一的响应结果,提高代码的可读性和可维护性。
我们把异常都放在这个类来处理,就不用在 controller 写那么多处理异常代码了。
@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();
}
}
在控制器中只需要完成对业务类的方法调用即可,不用去考虑异常怎么处理。
本次案例,介绍了三种处理异常的方式,我们学习了 SpringMVC 后就要使用全局的处理异常。比起原始的 try catch 语句块处理异常是不是简洁很多了,我们把同样的事情单独放在一个类中去完成,就不会像刚刚那样把所有代码都写在控制器中那么臃肿了。
使用Spring MVC异常处理的好处有以下几个方面:
统一异常处理:通过在全局配置中定义统一的异常处理类,可以集中处理应用程序中所有控制器抛出的异常。这样可以减少代码重复,提高代码的可维护性和可读性。
简化异常处理逻辑:在应用程序的控制器中,通常需要编写大量的异常处理代码来处理各种可能发生的异常情况。使用Spring MVC的异常处理功能,可以将这些异常处理逻辑从控制器中抽离出来,使得控制器代码更加简洁和清晰。
统一错误信息:通过自定义异常处理类,可以将不同类型的异常映射到统一的错误信息,并返回给客户端。这样可以提供一致的错误信息格式,便于前端进行处理和展示。
异常日志记录:在异常处理类中,可以对异常进行日志记录,包括异常的详细信息、发生异常的时间等。这样可以帮助开发人员快速定位和解决问题,提高系统的可靠性和稳定性。
提升用户体验:通过合理地处理异常,可以向用户提供更友好的错误提示信息,避免出现系统默认的错误页面或者错误信息。这样可以提升用户体验,增加系统的可用性。
总之,使用Spring MVC的异常处理功能可以简化开发过程,提高代码的可维护性和可读性,提供统一的错误信息和日志记录,并提升用户体验。这些优势使得Spring MVC成为开发Web应用程序时的首选框架之一。
地址:ch09 · qiuqiu/SpringMVC - 码云 - 开源中国 (gitee.com)