项目统一异常处理(未完待续...)

  1. 1. 1、使用web技术
  2. 2. 2、HandlerExceptionResolver异常解析器
    1. 2.1. 测试
      1. 2.1.1. 方法一:
      2. 2.1.2. 方法二:
  3. 3. 3 、异常解析器链
  4. 4. 4、@ExceptionHandler统一异常处理

​ 在J2EE项目的开发中,不管是对底层的数据库操作过程,还是业务层的处理过程,还是控制层的处理过程,都不可避免会遇到各种可预知的、不可预知的异常需要处理。每个过程都单独处理异常,系统的代码耦合度高,工作量大且不好统一,维护的工作量也很大。

​ 任何程序都会有异常。无论你是做什么项目,对异常的处理都是非常有必要的,尤其是web项目,因为它一般直接面向用户,所以良好的异常处理就显得格外的重要。

Java异常体系简介

​ Java相较于其它大多数语言提供了一套非常完善的异常体系Throwable:分为Error和Exception两大分支:

  1. Error:错误,对于所有的编译时期的错误以及系统错误都是通过Error抛出的,比如NoClassDefFoundError、硬件问题等等。

  2. Exception:异常,是更为重要的一个分支,是程序员经常打交道的。异常定义为是程序的问题,程序本身是可以处理的。异常Exception它本身还分为两大重要的分支:Checked Exception(可检查异常,如IOException)和Unchecked Exception(不可检查异常,如RuntimeException)。

    Error和Exception最大的区别是:异常是可以被程序处理的,而错误是没法处理的。

    ​ 错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况(比如类找不到NoClassDefFoundError)

    ​ 对于与数据库相关的 Spring MVC 项目,我们通常会把 事务 配置在 Service层,当数据库操作失败时让 Service 层抛出运行时异常,Spring 事物管理器就会进行回滚。

    ​ 如此一来,我们的 Controller 层就不得不进行 try-catch Service 层的异常,否则会返回一些不友好的错误信息到客户端。但是,Controller 层每个方法体都写一些模板化的 try-catch 的代码,很难看也难维护,特别是还需要对 Service 层的不同异常进行不同处理的时候。

    只要设计得当,就再也不用在 Controller 层进行 try-catch 了!

HTTP status codes:

异常类型 状态码
MissingPathVariableException 500
ConversionNotSupportedException 500
HttpMessageNotWritableException 500
AsyncRequestTimeoutException 503
MissingServletRequestParameterException 400
ServletRequestBindingException 400
TypeMismatchException 400
HttpMessageNotReadableException 400
MethodArgumentNotValidException 400
MissingServletRequestPartException 400
BindException 400
NoHandlerFoundException 404
HttpRequestMethodNotSupportedException 405
HttpMediaTypeNotAcceptableException 406
HttpMediaTypeNotSupportedException 415

1、使用web技术

​ 在还没有Spring,更无Spring Boot时,开发使用的是源生的Servlet + tomcat容器。其实它也是提供了通用的异常的处理配置方式的。

​ 使用web技术,需要在web.xml进行配置异常代码和异常跳转页面,然后程序中出现异常时,就会自动跳转到指定的异常页面。

1
2
3
4
5
6
7
8
9
10
11
<error-page>
<error-code>500</error-code>
<location>/500.jsp</location>
</error-page>

<!-- 根据异常类型 -->
<error-page>
<exception-type>java.lang.RuntimeException</exception-type>
<!- <error-code>500</error-code> -->
<location>/500.jsp</location>
</error-page>

2、HandlerExceptionResolver异常解析器

HandlerExceptionResolver接口仅有一个resolveException方法用于处理异常

resolveException方法将返回ModelAndView类型的结果,返回值有如下约定:

​ ModelAndView对象中可以包含包含响应的错误的数据和一个要转发到的错误视图。
如果异常在解析器中已被处理,并且不需要返回异常视图,则可以返回一个空的ModelAndView。
​ 如果异常仍未解决,则返回null,以便后面的异常解析器继续尝试解决,如果解析器链执行完毕异常仍未解决,则该异常将直接向上抛出到 Servlet 容器。

测试

(适合前后端不分离的情况)

方法一:

自定义异常类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.lan.pojo;

// 自定义异常类
public class SysException extends Exception{
private String message;

public SysException() {
}


public SysException(String message) {
this.message = message;
}

public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}
}

异常视图页面
1
2
3
4
5
6
7
8
9
10
11
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<head>
<title>异常页面</title>
</head>
<body>
<h1>出问题啦!</h1>
<br/>
<span style="color: #dc143c; ">${errorMsg}</span>
</body>
</html>

我们自定义一个异常解析器,用于将异常信息转发到error.jsp异常视图!

自定义异常处理器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.lan.exception;

import com.lan.pojo.SysException;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* 我们直接将异常解析器通过注解或者XML交接Spring管理即可,
* DispatcherServlet中会自动查找所有HandlerExceptionResolver的bean。
*/
@Component
public class SysExceptionResolver implements HandlerExceptionResolver {

/**
* 处理异常业务逻辑
*/
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
// 获取到异常对象
SysException e;
//异常类型区分 、若是自定义的业务异常,那就返回到单页面异常页面
if (ex instanceof SysException) {
e = (SysException) ex;
} else { // 否则统一到统一的错误页面
e = new SysException("系统正在维护....");
}
/*
* 创建ModelAndView对象
* 设置模型信息,即异常提示,以及需要跳转的异常视图名
*
*/
ModelAndView mv = new ModelAndView();
mv.addObject("errorMsg", e.getMessage());
mv.setViewName("error.jsp");
return mv;
}

}

我们准备一个Controller,分别模拟抛出SysException和其他异常!

测试异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.lan.controller;

import com.lan.pojo.SysException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class HandlerExceptionResolverController {
/**
* 模拟抛出SysException
*/
@RequestMapping("/err1")
public void handlerExceptionResolver() throws SysException {

throw new SysException("你的网络不好,请稍等…………");

}

/**
* 模拟抛出其他异常
*/
@RequestMapping("/err2")
public void handlerExceptionResolver2() {
throw new RuntimeException();
}

@RequestMapping("/err3")
public void handlerExceptionResolve3r() throws SysException {

throw new SysException("输入错误…………");

}

}

结果如下:

方法二:

MyExceptionResolver.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.xxxx.smvc.resolver;

import com.xxxx.smvc.exception.Exception01;
import com.xxxx.smvc.exception.Exception02;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class MyExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {

ModelAndView mv = new ModelAndView();
// 获取异常信息
mv.addObject("msg", ex.getMessage());
if (ex instanceof Exception01) {
mv.setViewName("error.jsp");
} else if (ex instanceof Exception02) {
mv.setViewName("error2.jsp");
} else {
mv.setViewName("error3.jsp");
}
return mv;
}
}

ExceptionController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.xxxx.smvc.controller;

import com.xxxx.smvc.exception.Exception01;
import com.xxxx.smvc.exception.Exception02;
import com.xxxx.smvc.exception.Exception03;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

//@RequestMapping("/ex")
@Controller
public class ExceptionController {

@GetMapping("/test1")
public String test1(@RequestParam("id") Integer id) {
if (id==1) {
throw new Exception01("1号异常....");
}
return "success.jsp";
}

@GetMapping("/test2")
public String test2(@RequestParam("id") Integer id) {
if (id==2) {
throw new Exception02("2号异常....");
}
return "success.jsp";
}

@GetMapping("/test3")
public String test3(@RequestParam("id") Integer id) {
if (id==3) {
throw new Exception03("3号异常....");
}
return "success.jsp";
}

}
Exception01.java
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.xxxx.smvc.exception;

// 运行时异常
public class Exception01 extends RuntimeException {

public Exception01() {
}

public Exception01(String message) {
super(message);
}
}

Exception02、Exception03同上….

测试结果:


3 、异常解析器链

​ 我们可以通过在 Spring 配置中声明多个HandlerExceptionResolver的 bean,并根据需要设置其顺序属性来形成异常解析器链。并且支持order排序,order值越大,异常解析器的在链中的位置越靠后(解析器链实际上是一个List集合)。我们通过实现Ordered接口或者使用@Order注解来确定order值,如果不配置order值,那么默认为最大值,即Integer.MAX_VALUE,也就是说在链尾部!

​ 当抛出异常时,DispatcherServlet将会依次调用异常解析器链的每一个析器的resolveException方法,如果当前异常解析器的resolveException方法返回null,那么表示未能成功处理该异常,那么继续调用下一个异常解析器,否则,表示异常处理器成功,不会继续调用后续的解析器!

​ 如:新建另一个异常解析器SysExceptionResolver2,它的order值为0,小于2,因此它将会被先于SysExceptionResolver调用!

​ 如果将SysExceptionResolver2的@Order注解值改为大于2,或者去掉该注解,那么SysExceptionResolver将会先被调用:

4、@ExceptionHandler统一异常处理

​ Spring提供了HandlerExceptionResolver的几个默认实现,它们具有自己的可扩展的解决异常的方式,我们完全可以直接利用这些默认实现,只需要配置对应的异常处理的方案,而无需再自定义HandlerExceptionResolver,无需自己编写完整的异常处理逻辑!

​ ExceptionHandlerExceptionResolver通过调用在@Controller类或者@ControllerAdvice类中的具有@ExceptionHandler注解的方法来解决来自Controller方法的异常,这实际上就是一种非常常用并且简单的处理异常的方式,在目前的项目中,大多使用该方式!

## 4.1 @ExceptionHandler测试