Spring MVC 异常处理

1. 前言

本章节将和大家一起聊聊 Spring MVC 是如何处理异常的。

Spring MVC 框架中,将异常处理从业务逻辑中解耦出来,既保证了业务逻辑高度内聚性,又能实现了异常信息的统一处理和维护。很优雅的解决了异常的问题。通过本章节内容的学习,你将了解到 Spring MVC 内置的异常处理机制。

  • 将异常映射成 HTTP 状态码;
  • 全局异常处理器的使用。这个将是本章节的重点;
  • 与异常有关的注解。

2. 异常处理原则

异常是程序运行过程中不可避免的问题。异常出现的原因很多,但不管怎样,都需要提前预知或者当异常发生后采取相应的处理措施。

异常的处理原则是:

  • 能预知的尽可能在逻辑层面提前制止。如用户注册时,要求登录名是唯一的,可先检查数据库是否存在同名用户名后,再进行添加操作;
  • 以一种友好的方式告知使用者出错的原因;
  • 采用多层体系结构的项目中,建议异常由下逐层向上抛出,一直到达应用层面;
  • 使用日志记录功能把异常信息记录在日志文件中,便于开发者分析。

如下面的控制器方法:

@Controller
public class ExceptionAction {
@RequestMapping("/exception01")
public String exception01(@RequestParam("userName") String userName) {	
	return "exception";
}
}

在浏览器中输入:http://localhost:8888/sm-demo/exception01 ,页面中会出现错误提示。

图片描述

这个原因是 @RequestParam(“userName”) 注解在默认情况下,要求请求包中一定要有 userName 这个参数。

显然,页面中显示出来的错误信息是不友好的。所谓的异常处理,并不能完全阻止异常的发生。而是把异常信息对外、对内做一个封装,换一个浅白的、直接的、非专业的方式告诉使用者。

对于前面的异常解决方案,可以在 @RequestParam(value = “userName”,required = false) 中添加一个 required = false 的设置。这是一种最理想的异常解决方案。

3. Spring MVC 的异常处理方案

3.1 将异常映射成状态码

Spring MVC 内部提供了很多优雅的异常处理机制。其中之一就是把不同的异常映射成 HTTP 状态码。

如下面实例:

  1. 首先定义一个异常类:
@ResponseStatus(value = HttpStatus.FORBIDDEN, reason = "数字范围不符合要求!(不能是 20)")
public class NumberException extends RuntimeException {	
}
  1. 如果在方法中抛出的异常已经被映射成了状态吗。则在浏览器中显示出来的异常信息会明确很多。
@RequestMapping("/exception")
public String test(@RequestParam("num") int num) {
	if (num == 20) {
		throw new NumberException();
	}
		return "success";
}

在浏览器中输入 http://localhost:8888/sm-demo/exception?num=13 。可以看到:

图片描述

定制化的信息已经有所改善,但是,还是不够友好。

3.2 全局异常处理器

Spring MVC 提供了名为 SimpleMappingExceptionResolver 的异常处理组件,该组件实现了 HandlerExceptionResolver 接口,或者说实现了这个接口的对象都可称其为全局异常处理器

何谓全局异常处理器?

通俗讲,有点类似于前端控制器的设计思路。Spring MVC 把所有异常分离出来后通通交给全局异常处理器做集中处理。

使用流程:

  1. 打开项目中的 WebConfig 配置类,添加组件;
@Bean
public SimpleMappingExceptionResolver simpleMappingExceptionResolver() {
	SimpleMappingExceptionResolver simResolver=new SimpleMappingExceptionResolver();
	//异常处理页面
	simResolver.setDefaultErrorView("error");
	//封装异常信息的属性名,默认是 exception
	simResolver.setExceptionAttribute("exception");
    //添加自定义异常信息
	Properties mappings=new Properties();
	mappings.put("com.mk.web.exception.MyException", "/WEB-INF/jsp/exception.jsp");
	simResolver.setExceptionMappings(mappings);
	return simResolver;
}

代码中有注解,不再多言。

  1. 自定义异常类。自定义异常类并不是必须的,项目中自定义异常的目的可以让异常的语义更具体;
public class MyException extends Exception {
   public MyException() {	
   }
   public MyException(String msg) {
   	super(msg);
   }
}
  1. 编写控制器。控制器中的方法会根据 userName 的值决定是否抛出异常。
   @RequestMapping("/exception03")
   public String exception03(String userName) throws MyException {
   	if (StringUtils.isEmpty(userName)) {
   		throw new MyException("用户名不能为空");
   	}
   	return "index";
   }
  1. 测试。打开浏览器,输入 http://localhost:8888/sm-demo/exception03
  2. 浏览器会显示把错误导向到 “WEB-INF/exception.jsp” 页面。此页面,可添加下面的代码。
   <body>
   出错啦!${exception.message}
   </body> 

解析出错误的具体信息,最后可以在浏览器中看到:

图片描述

开发者可以根据需要编写自己的全局异常处理器组件。

3.3 与异常有关的注解

Spring MVC 还可以使用注解的方式集中处理异常。

@ExceptionHandler 注解:

此注解所标注的方法能够处理同一个控制器中所有方法所抛出的异常。

如下面实例:

@ExceptionHandler(Exception.class)
public String exception() {
	return "exception";
}
@RequestMapping("/exception04")
public String exception04(String userName) throws Exception {
	if (StringUtils.isEmpty(userName)) {
		throw new Exception("用户名不能为空");
	}
	return "index";
}

当控制器中的方法抛出异常后,会由 exception() 方法统一捕获,然后跳到指定页面。

@ControllerAdvice 注解:

此注解放在类的前面,且类中可以包含一个或多个如下类型的方法。

Tips: 由 @ControllerAdvice 注解标注的类本质上就是一个基于 AOP 思想的拦截器。

  • @ExceptionHandler 注解标注的方法;
  • @InitBinder 注解标注的方法;
  • @ModelAttribute 注解标注的方法。

@ControllerAdvice 实现案例如下:

@ControllerAdvice
public class ExceptionAdviceAction {
	@ExceptionHandler(Exception.class)
	public String exception() {
		return "exception";
	}
}

无论哪一个控制器中抛出异常,都会由 ExceptionAdviceAction 类中的 exception() 方法统一响应处理。

4. 小结

本章节和大家讲解了 Spring MVC 中如何优雅的处理异常。主要有 3 种方案:

  • 将异常映射成为 HTTP 状态码;
  • 使用全局异常处理组件。建议大家使用这种方式,具有很多的隔离性、统一性;
  • 使用注解的方式处理异常。