目录
5.1、我们可以自定义错误页面来覆盖springboot默认的错误页面:
(1)BasicErrorController 类,默认处理/error请求的类的源码。
(2)DefaultErrorAttributes类,封装了默认错误数据
(3)DefaultErrorViewResolver类,默认错误视图解析器
5.2、某个异常单独处理 @ExceptionHandler注解
5.3、全局异常处理:注入SimpleMappingExceptionResolver
5.4、自定义 HandlerExceptionResolver 类处理异常(不常用)
5.异常处理
SpringBoot 框架异常处理有五种处理方式,从范围来说包括有全局异常捕获处理方式和局部异常捕获处理方式,接下来通过使用下面的后端代码一一对这五种捕获方式讲解。
总体来讲,SpringBoot 处理异常有下面几个。
5.1、我们可以自定义错误页面来覆盖springboot默认的错误页面:
(一)、SpringBoot 默认错误处理机制源码解析
如果没有进行处理出现错误,如果是浏览器访问,它会返回一张 html 的错误页面,
如果是其他客户端访问出现异常,它会直接返回json格式的异常数据。
在ErrorMvcAutoConfiguration中,SpringBoot 错误处理机制源码跟踪
(1)BasicErrorController 类,默认处理/error请求的类的源码。
其中有两个方法处理错误请求:
- ModelAndView
- ResponseEntity
返回的异常信息可以在 DefaultErrorAttributes 类中查询
页面能获取的信息:
- timestamp:时间戳
- status:状态码
- error:错误提示
- exception:异常对象
- message:异常消息
- errors:JSR303数据校验的错误都在这里
//以下是从静态资源文件夹下找viewName对应的错误页面的方法源码
private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
for (String location : this.resources.getStaticLocations()) {
try {
Resource resource = this.applicationContext.getResource(location);
resource = resource.createRelative(viewName + ".html");
if (resource.exists()) {
return new ModelAndView(new HtmlResourceView(resource), model);
}
}
catch (Exception ex) {
}
}
return null;
}
(2)DefaultErrorAttributes类,封装了默认错误数据
源码如下:
(3)DefaultErrorViewResolver类,默认错误视图解析器
源码如下
结论:
- 有模板引擎的情况下
error/状态码 -- 将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的error文件夹下(/templates/error/xxx.html),发生此状态码的错误就会来到 对应的页面。使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,精确状态码优先。
- 没有模板引擎(模板引擎找不到这个错误页面)
SpringBoot会去静态资源目录(static/error/状态码.html),下面找错误代码命名的页面。
- 完整的错误页面查找方式
发送 URL 请求,当遇到 404、500 等错误时,需要访问/error/404时,
①此时模板引擎就生效了,它会对路径进行拼接,拼接后的路径为:classpath:/template/error/404.html。模板引擎就会去该路径下查找是否有一个 404.html页面,有的话就会将其返回;
②如果没使用模板引擎的话,就去Thymeleaf 规定的静态资源文件夹下找对应的 /error/404.html页面;
③都没有的话,返回 Spring Boot 为我们提供的默认错误页面。
(二)、自定义异常页面覆盖默认异常页面
- 4xx 客户端错误
- 5xx 服务端错误
错误页面优先级
定制 Spring Boot 错误页面所有方式优先级顺序为:
自定义动态错误页面(精确匹配)>自定义静态错误页面(精确匹配)>
自定义动态错误页面(模糊匹配)>自定义静态错误页面(模糊匹配)>
自定义 error.html
从前面可以看出,共有两个优先级原则,分别是:精确 > 模糊、动态 > 静态,但是这两者的优先级原则是:前者 > 后者,我想这并不难理解。
在 SpringBoot 中自定义异常页面,就页面属性而言,可以分为两类,
①静态异常页面
常见的异常可以分为两个派系,分别是 400 系列和 500 系列。
自定义异常页面也可以分为两类:
一类是以 HTTP 响应码来命名的,例如:402.html、404.html、500.html 等等,
另一类则是直接定义一个 4xx.html,状态响应码在 400-499 范围内都显示 4xx.html 异常页面,5xx.html 包含 500-599 范围内的状态响应码都显示 5xx.html 异常页面。
默认情况下,是在 classpath:/static/error/ 下定义异常页面,如下:
②动态异常页面
其实动态异常页面定义与静态异常页面的方式相同,可以采用的模板技术包含:jsp、freemarker、thymeleaf。
动态异常页面命名可以是404、500等等精确的状态码命名方式,
一般情况下,由于动态异常页面可以直接展示异常详细的信息,所以没有必要挨个枚举了,这里就直接定义为4xx.html、5xx.html。(这里采用的是thymeleaf)
动态异常页面定义如下:
【5xx.html】
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>templates-5xx</h1>
<tr>
<td>path</td>
<td th:text="${path}"></td>
</tr>
<tr>
<td>timestamp</td>
<td th:text="${timestamp}"></td>
</tr>
<tr>
<td>message</td>
<td th:text="${message}"></td>
</tr>
<tr>
<td>error</td>
<td th:text="${error}"></td>
</tr>
<tr>
<td>status</td>
<td th:text="${status}"></td>
</tr>
</body>
</html>
默认情况下,会展示5条异常相关信息,如下:
【划重点啦】
如果动态页面和静态页面都定义了异常处理页面,
例如:classpath:/templates/error/404.html和classpath:/static/error/404.html两者同时存在,如果抛出异常,默认使用动态异常页面。这里也有一个优先级原则:动态 > 静态
(三)、自定义异常数据
一般情况下,在 SpringBoot 中,异常信息就是下面展示的 5 条,如下:
这 5 条异常数据定义在org.springframework.boot.web.servlet.error.DefaultErrorAttributes中的getErrorAttributes方法中,
源码如下:
因为该方法里面还存在方法,将其整理成简化版本的,如下:
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap<>();
errorAttributes.put("timestamp", new Date());
errorAttributes.put("status", status);
errorAttributes.put("error",HttpStatus.valueOf(status).getReasonPhrase());
errorAttributes.put("message", StringUtils.isEmpty(message) ? "No message available" : message);
errorAttributes.put("path", path);
return errorAttributes;
}
DefaultErrorAttributes类本来定义在ErrorMvcAutoConfiguration异常自动配置类中的errorAttributes方法中定义,如下:
如果开发者未自己提供 ErrorAttributes 实例,则 SpringBoot 会默认提供一个ErrorAttributes 实例,即DefaultErrorAttributes。
基于该原则,开发者自定义ErrorAttributes 实例有两种方式,如下:
①直接实现 ErrorAttributes 接口
②继承 DefaultErrorAttributes(推荐),因为 DefaultErrorAttributes 中对异常数据的处理已经完成,开发者可以直接使用。
具体定义如下:
@Component
public class MyErrorAttribute extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
map.put("myerror","这是我自定义的异常!");
return map;
}
}
自定义ErrorAttributes后,记得使用@Component注解成一个Bean,这样SpringBoot就不会使用默认的DefaultErrorAttributes了,大家可以使用debug试一下。
(四)、自定义异常视图
异常视图页面默认的就是前面提到的静态异常页面和动态异常页面,当然这个也是可以自定义的。首先,默认异常页面加载逻辑在org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController类中的errorHtml方法中,这个方法用来返回异常页面+数据,除此之外,还有个error方法,该方法用于返回异常数据(如果是Ajax请求,该方法则会被触发)。
在该方法中,首先会通过getErrorAttributes方法获取异常数据(实际上会调用到 ErrorAttributes 类中的 getErrorAttributes 方法),然后会调用resolveErrorView方法去创建一个ModelAndView,如果创建失败,那么就会看到默认的错误提示页面了。
一般情况下,resolveErrorView方法会到DefaultErrorViewResolver类中的resolveErrorView方法中:
在这里,首先会以状态码作为视图名去精确的查找动态异常页面和静态异常页面,如果没有找到,就会以 4xx 或 5xx 再分别去查找动态异常页面和静态异常页面。
其实,要自定义异常视图解析,也比较容易,由于ErrorMvcAutoConfiguration类中默认提供了默认的视图解析实例DefaultErrorViewResolver,如果开发者未提供相关实例,则使用默认实例,若提供了相关实例,则默认实例就会失效。
因此,自定义异常视图,只需要提供一个ErrorViewResolver即可,如下:
@Component
public class MyErrorViewResolver extends DefaultErrorViewResolver {
public MyErrorViewResolver(ApplicationContext applicationContext, ResourceProperties resourceProperties) {
super(applicationContext, resourceProperties);
}
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
ModelAndView view = new ModelAndView();
view.setViewName("mangoError");
view.addAllObjects(model);
return view;
}
}
mangoError就是自定义异常视图名,这里也可以自定义异常数据(直接在resolveErrorView方法中定义一个model,然后将参数中的model拷贝过去即可,可以在新的model中添加或删除异常数据,值得注意的是参数中的model类型是UnmodifiableMap,即不可直接修改的Map),而不需要自定义MyErrorAttributes。自定义完成后,提供一个名为mangoError的视图,如下:
5.2、某个异常单独处理 @ExceptionHandler注解
某个异常处理主要用到 @ExceptionHandler 注解,此注解加到类的方法上,当此注解里定义的异常抛出时,该方法会被执行。
如果 @ExceptionHandler 所在的类是 @Controller 注解,则此方法只作用在此controller类中。
如果 @ExceptionHandler 所在的类是 @ControllerAdvice 注解,则此方法会作用在全局。
1、特定controller处理异常
@Controller + @ExceptionHandler
@Controller
public class UserController {
@GetMapping("/userList")
public String userList(Model model){
int i = 1/0;//抛出异常
List<User> userList = userService.getAllUser();
model.addAttribute("userList", userList);
return "userList";
}
//局部异常处理 - 页面
@ExceptionHandler(value = {java.lang.ArithmeticException.class})
public ModelAndView exHandler(Exception ex){
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("err", ex.getMessage()); //更加自由显示处理异常
modelAndView.setViewName("/myError");
return modelAndView;
}
/* //局部异常处理 - JSON格式
@ExceptionHandler(value = {java.lang.Exception.class})
@ResponseBody
public String exHandler(Exception e){
// 判断发生异常的类型是除0异常则做出响应
if(e instanceof ArithmeticException){
return "发生了除0异常";
}
// 未知的异常做出响应
return "发生了未知异常";
}*/
}
2、全局controller处理异常
@ControllerAdvice + @ExceptionHandler
在spring 3.2中,新增了 @ControllerAdvice 注解,可以用于定义 @ExceptionHandler、@InitBinder、@ModelAttribute,并应用到所有@RequestMapping中。
简单的说,进入Controller层的错误才会由 @ControllerAdvice 处理,拦截器抛出的错误以及访问错误地址的情况 @ControllerAdvice 处理不了,由SpringBoot默认的异常处理机制处理。
@ControllerAdvice
public class DoAllErrotController {
//全局异常处理 - 视图
@ExceptionHandler(value = {java.lang.ArithmeticException.class})
public ModelAndView exHandler(Exception ex){
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("err", ex.getMessage()); //更加自由显示处理异常
modelAndView.setViewName("/myError");
return modelAndView;
}
//全局异常处理 - JSON格式
/*
@ExceptionHandler(value = {RuntimeException.class})
@ResponseBody
public Map<String, Object> errorJson(){
Map<String, Object> errorMap = new HashMap<>();
errorMap.put("errorCode", "500");
errorMap.put("errorMessage", "发送异常啦!");
return errorMap;
}
*/
}
特定的controller内部处理和全局controller异常处理同时处理时, 以特定的controller处理优先
3、定义全局数据
使用@ControllerAdvice
我们可以通过使用@ControllerAdvice定义全局数据,在所有的Controller类中获取某个或者多个全局的数据
@ControllerAdvice
public class MyGlobalData {
// 自定义kv。key - globalKey1,v - Map。
@ModelAttribute(value = "globalKey1")
public Map<String, Object> globalData(){
Map<String, Object> map = new HashMap<>();
map.put("aaa","vvv1");
map.put("bbb","vvv2");
return map;
}
}
@Controller
public class ErrorController {
@GetMapping("/get")
@ResponseBody
public String get(Model model){
Map<String, Object> map = model.asMap();
map.forEach((k,v) -> {
System.out.println(k);
System.out.println(v);
});
return "success";
}
}
5.3、全局异常处理:注入SimpleMappingExceptionResolver
springboot 配置SimpleMappingExceptionResolver
在@Configuration注解的配置类中,注入SimpleMappingExceptionResolver,@bean配置来处理做全局异常处理,但是这种方法不能返回异常的具体信息
@Configuration
public class MyErrorConfig {
@Bean
public SimpleMappingExceptionResolver simpleMappingExceptionResolver(){
SimpleMappingExceptionResolver simpleMappingExceptionResolver = new SimpleMappingExceptionResolver();
Properties properties = new Properties();
properties.put("java.lang.NullPointerException","myError"); //参数:异常类型,视图名称
properties.put("java.lang.ArithmeticException","myError");
simpleMappingExceptionResolver.setExceptionMappings(properties); //设置异常与视图映射信息的,默认通过${exception} 获取,可修改
simpleMappingExceptionResolver.setExceptionAttribute("err"); //
return simpleMappingExceptionResolver;
}
}
注入bean的配置和@ControllerAdvice方式同时使用时,优先是@ControllerAdvice方式处理。
SpringMVC配置SimpleMappingExceptionResolver
在springmvc.xml配置文件中对SimpleMappingExceptionResolver的配置:
比如项目中的注册功能需要有对用户名已经存在,密码格式不符合等条件判断作出反应。
我们先自定义一个异常实体类,定义message属性用于存放异常的信息
public class RegisterException extends Exception{
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
private String message;
public RegisterException(String message){
this.message=message;
}
}
在service层中抛出异常
List<Users> usersList = usersMapper.selectByExample(example);
if (usersList.size()>0)
throw new RegisterException("用户名已经存在");
最后在springmvc.xml 中配置SimpleMappingExceptionResolver
<!-- springmvc提供的简单异常处理器 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<!-- 定义默认的异常处理页面 -->
<property name="defaultErrorView" value="/WEB-INF/jsp/error.jsp"/>
<!-- 定义异常处理页面用来获取异常信息的变量名,也可不定义,默认名为exception -->
<property name="exceptionAttribute" value="ex" />
<!-- 定义需要特殊处理的异常,这是重要点 -->
<property name="exceptionMappings">
<props>
<prop key="包路径.RegisterException">register</prop>
</props>
<!-- 还可以定义其他的自定义异常 -->
</property>
</bean>
我们在这里的exceptionMappings中定义一下当出现这个异常时候,跳转到哪个页面,这里我选择跳转到register这个页面。
最后在jsp中输出我们的错误信息
<h1 class="text-center" style="margin-bottom: 30px">用户注册</h1>
<center><h5 style="color:red">${ex.message}</h5></center>
<!-- ex是异常对象,在springmvc的exceptionAttribute中可配置名称,默认是exception -->
5.4、自定义 HandlerExceptionResolver 类处理异常(不常用)
需 要 在 全 局 异 常 处 理 类 中 实 现 HandlerExceptionResolver 接口,并添加@Configuration注解。
@Configuration
public class MyErrorConfig5 implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
ModelAndView modelAndView = new ModelAndView();
if(e instanceof ArithmeticException){
modelAndView.setViewName("myError");
}
if(e instanceof NullPointerException){
modelAndView.setViewName("error");
}
modelAndView.addObject("err",e);
return modelAndView;
}
}