七、SpringMVC 异常处理
7.1 异常分类
系统中异常包括两类:编译期异常和运行时异常。
- 编译期异常:继承java.lang.Exception,通过捕获异常(try-catch)从而获取异常信息。
- 运行时异常:继承java.lang.RuntimeException,通过规范代码开发、测试通过手段减少运行时异常的发生。
7.2 SpringMVC的异常处理机制
在 Spring MVC 应用的开发中,不管是操作底层数据库,还是业务层或控制层,都会不可避免地遇到各种可预知的、不可预知的异常。我们需要捕捉处理异常,才能保证程序不被终止。
系统中的三层架构(dao、service、controller)都通过throws Exception向上抛出,最后由Spring MVC前端控制器交由`异常处理器`进行异常处理,如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AIz3WntE-1655368987527)(images/dt_20220425165922.jpg)]
Spring MVC 有以下 3 种处理异常的方式:
- 使用 Spring MVC 提供的简单异常处理器 SimpleMappingExceptionResolver。
- 实现 Spring 的异常处理接口 HandlerExceptionResolver,自定义自己的异常处理器。
- 使用 @ExceptionHandler 注解实现异常处理。
7.3 异常模拟过程
7.3.1 maven依赖
<dependencies>
<!-- spring核心包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.18.RELEASE</version>
</dependency>
<!-- springbean包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.3.18.RELEASE</version>
</dependency>
<!-- springcontext包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.18.RELEASE</version>
</dependency>
<!-- spring表达式包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>4.3.18.RELEASE</version>
</dependency>
<!-- springAOP包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.18.RELEASE</version>
</dependency>
<!-- springAspects包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.18.RELEASE</version>
</dependency>
<!-- spring对web的支持 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.3.18.RELEASE</version>
</dependency>
<!-- springwebMVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.18.RELEASE</version>
</dependency>
<!-- 配置javaweb环境 -->
<!-- servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- jsp-api -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<!-- jstl -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.5</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.5</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.5</version>
</dependency>
</dependencies>
7.3.2 SpringMVC配置
web.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">
<!--字符编码过滤器-->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 通过隐藏域传参解决form表单的PUT与DELETE方式的请求 -->
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 解决put请求传递参数的问题-->
<filter>
<filter-name>HttpPutFormContentFilter</filter-name>
<filter-class>org.springframework.web.filter.HttpPutFormContentFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HttpPutFormContentFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 配置前端控制器 -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>/views/index.jsp</welcome-file>
</welcome-file-list>
</web-app>
springmvc.xml:
<?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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- springmvc的注解式开发 -->
<!-- 开启组件扫描 -->
<context:component-scan base-package="com.newcapec"/>
<!-- MVC注解驱动 -->
<mvc:annotation-driven/>
<!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 配置视图地址的前缀和后缀:简化视图地址 -->
<property name="prefix" value="/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!--不拦截静态资源-->
<mvc:default-servlet-handler/>
</beans>
7.3.3 自定义异常类
BusinessExeption.java:
public class BusinessExeption extends Exception {
public BusinessExeption(){
super("业务异常");
}
}
CustomException.java:
public class CustomException extends Exception {
public CustomException(){
super("自定义异常");
}
}
7.3.4 dao
接口:
public interface DemoDao {
void insert(String name) throws Exception;
}
实现类:
@Repository("demoDao")
public class DemoDaoImpl implements DemoDao {
@Override
public void insert(String name) throws Exception {
System.out.println("dao执行了..." + name);
//模拟异常
if ("null".equals(name)) {
System.out.println("空指针异常出现...");
String s = null;
s.trim();
} else if ("business".equals(name)) {
System.out.println("业务异常出现...");
throw new BusinessExeption();
} else if ("custom".equals(name)) {
System.out.println("自定义异常出现...");
throw new CustomException();
} else {
System.out.println("未出现异常,程序正常...");
}
}
}
7.3.5 service
接口:
public interface DemoService {
void add(String name) throws Exception;
}
实现类:
@Service("demoService")
public class DemoServiceImpl implements DemoService {
@Autowired
private DemoDao demoDao;
@Override
public void add(String name) throws Exception {
System.out.println("service执行了...");
demoDao.insert(name);
}
}
7.3.6 controller
@Controller
@RequestMapping("/demo")
public class DemoController {
@Autowired
private DemoService demoService;
@RequestMapping("/add")
public String add(String name) throws Exception {
System.out.println("controller执行了...");
//在controller中调用业务逻辑层
demoService.add(name);
return "success";
}
}
7.3.7 页面
index.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<base href="${pageContext.request.contextPath}/">
<title>Title</title>
</head>
<body>
<div style="text-align: center">
<h1>异常处理</h1>
<form action="demo/add" method="post">
<p>数据:<input type="text" name="name"></p>
<p><button>提交</button></p>
</form>
</div>
</body>
</html>
success.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<base href="${pageContext.request.contextPath}/">
<title>Title</title>
</head>
<body>
<div style="text-align: center;">
<h1>跳转成功!!!</h1>
</div>
</body>
</html>
7.4 SpringMVC的异常处理
7.4.1 自带异常处理器
全局异常处理可使用 SimpleMappingExceptionResolver 来实现。它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常。
使用Spring MVC提供的简单异常处理器SimpleMappingExceptionResolver,在springmvc.xml配置如下:
<!-- 配置全局异常处理器 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<!-- 配置默认的异常处理页面,value属性配置为一个逻辑视图名 -->
<property name="defaultErrorView" value="error"/>
<!-- 配置异常处理页面用来获取异常信息的变量名,默认名为exception -->
<property name="exceptionAttribute" value="ex"/>
<!-- 定义需要特殊处理的异常,用类名或完全路径名作为key,异常页名作为值 -->
<property name="exceptionMappings">
<props>
<!-- key为单独处理异常的全限定名,value为单独处理异常之后的跳转页面 -->
<prop key="com.newcapec.exception.BusinessExeption">error_bus</prop>
<prop key="com.newcapec.exception.CustomException">error_cust</prop>
</props>
</property>
</bean>
异常跳转页面如下:
error.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<base href="${pageContext.request.contextPath}/">
<title>Title</title>
</head>
<body>
<div style="text-align: center">
<h1 style="color: red;">系统故障,请联系管理员!</h1>
<p>异常信息:${ex}</p>
<p>自定义信息:${msg}</p>
</div>
</body>
</html>
error_bus.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<base href="${pageContext.request.contextPath}/">
<title>Title</title>
</head>
<body>
<div style="text-align: center;">
<h1>业务异常出现后的跳转页面</h1>
<p>异常信息:${ex}</p>
<p>自定义信息:${msg}</p>
</div>
</body>
</html>
error_cust.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<base href="${pageContext.request.contextPath}/">
<title>Title</title>
</head>
<body>
<div style="text-align: center;">
<h1>自定义异常出现后的跳转页面</h1>
<p>异常信息:${ex}</p>
<p>自定义信息:${msg}</p>
</div>
</body>
</html>
7.4.2 自定义异常处理器
Spring MVC 通过 HandlerExceptionResolver 处理程序异常,包括处理器异常、数据绑定异常以及控制器执行时发生的异常。HandlerExceptionResolver 仅有一个接口方法,源码如下。
发生异常时,Spring MVC 会调用 resolveException() 方法,并转到 ModelAndView 对应的视图中,返回一个异常报告页面反馈给用户。
public interface HandlerExceptionResolver {
@Nullable
ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}
实现SpringMVC的异常处理接口HandlerExceptionResolver定义自己的异常处理器,代码如下:
/**
* 自定义全局异常处理器
* 1.实现HandlerExceptionResolver接口并实现其中抽象方法
* 2.将异常处理器对象放入ioc容器中
*
* 优点:比自带的SimpleMappingExceptionResolver异常处理器更加灵活,可以自定义业务处理。
* 缺点:业务实现比较麻烦,对异步请求时出现的异常,处理复杂
*/
@Component
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
/**
* 处理异常的方法
* @param request 请求对象
* @param response 响应对象
* @param handler 处理器对象
* @param ex 抛出的异常对象
* @return ModelAndView对象
*/
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
ModelAndView mav = new ModelAndView();
// 根据不同异常向不同的页面跳转
if(ex instanceof BusinessExeption){
request.setAttribute("msg", "message for business exception");
mav.setViewName("error_bus");
}else if(ex instanceof CustomException){
request.setAttribute("msg", "message for custom exception");
mav.setViewName("error_cust");
}else {
request.setAttribute("msg", "message for other exception");
mav.setViewName("error");
}
request.setAttribute("ex", ex);
//还可以做日志记录...
return mav;
}
}
7.4.3 注解式异常处理器
7.4.3.1 局部异常处理
在Controller中使用@ExceptionHandler注解实现异常处理。
@Controller
@RequestMapping("/other")
public class OtherController {
@RequestMapping("/add")
public String add() throws Exception {
System.out.println("其他处理器中的异常...");
//模拟异常
Class.forName("abc");
return "success";
}
/**
* 在Controller中定义一个处理异常的方法(一个Controller里面可以定义多个处理异常的方法)
* 方法的返回值是String类型,表示异常跳转的路径视图名
*
* @ExceptionHandler
* 位置:方法
* 作用:使当前方法可处理异常
* 属性:value = 当前方法将要处理异常的类型(异常的Class类型)
*
* 注意:如果@ExceptionHandler使用在某个Controller,那么它只能处理该Controller抛出的异常
*/
@ExceptionHandler(Exception.class)
public String handleException(Model model){
model.addAttribute("msg", "注解式异常处理");
return "error";
}
}
注意:进行异常处理的方法必须与抛出异常的方法在同一个Controller里面,不能全局控制异常。
7.4.3.2 全局异常处理
使用@ControllerAdvice + @ExceptionHandler注解实现全局异常处理。
/**
* @ControllerAdvice 位置:类
* 作用:注解式全局异常处理器
*/
@ControllerAdvice
public class MyControllerAdvice {
@ExceptionHandler(BusinessExeption.class)
public String handleBusinesException(Model model) {
model.addAttribute("msg", "注解式异常处理的商务异常");
return "error_bus";
}
@ExceptionHandler(CustomException.class)
public String handleCustomException(Model model) {
model.addAttribute("msg", "注解式异常处理的客户异常");
return "error_cust";
}
@ExceptionHandler(Exception.class)
public String handleException(Model model) {
model.addAttribute("msg", "注解式异常处理的其他异常");
return "error";
}
}
7.4.3.3 异步异常处理
在异常处理的方法中,使用@ResponseBody注解响应客户端即可。
Controller中的方法:
@ResponseBody
@RequestMapping("/get")
public String get(String id) throws Exception {
System.out.println("处理异步请求的方法执行了...");
//模拟异常
int i = Integer.parseInt(id);
System.out.println("编号:" + i);
return "success";
}
处理异常的方法:
/**
* 处理异步请求中出现的异常
*/
@ResponseBody
@ExceptionHandler(value = NumberFormatException.class)
public String handleAsyncException(){
return "error";
}
页面中的异步请求:
<script type="text/javascript" src="js/jquery-3.3.1.min.js"></script>
<script type="text/javascript">
function get() {
$.ajax({
url : "demo/get",
type : "get",
data : {
id : "10"},
success : function (data) {
alert(data);
}
});
}
</script>
<p><a href="javascript:get()">异步请求</a></p>
rn “success”;
}
**处理异常的方法:**
```java
/**
* 处理异步请求中出现的异常
*/
@ResponseBody
@ExceptionHandler(value = NumberFormatException.class)
public String handleAsyncException(){
return "error";
}
页面中的异步请求:
<script type="text/javascript" src="js/jquery-3.3.1.min.js"></script>
<script type="text/javascript">
function get() {
$.ajax({
url : "demo/get",
type : "get",
data : {
id : "10"},
success : function (data) {
alert(data);
}
});
}
</script>
<p><a href="javascript:get()">异步请求</a></p>