1. SpringBoot默认的错误处理机制
SpringBoot 默认的已经提供了一套处理异常的机制。一旦程序中出现了异常 SpringBoot 会向 /error 的 url 发送请求。 在 springBoot 中提供了一个叫 BasicExceptionController/BasicErrorController 来处理/error 请求, 然后跳转到默认显示异常的页面来展示异常信息。
2. 定制错误响应页面
2.1 全局异常界面
操作起来很简单,创建一个HTML文件,文件名称必须要error
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>错误提示</title>
</head>
<body>
程序错误!
<span th:text="${exception}"></span>
</body>
</html>
注意:th:text="${exception}" 可以获取异常描述信息
例如:
该方式一般适用于统一异常处理,粒度较粗。
2.2 有模板引擎
即有thymeleaf的时候。
2.2.1 精确匹配
将错误页面命名为 错误状态码.html 放在模板引擎文件夹(templates)下的error文件夹下,发生此状态码的错误就会来到 对应的页面
例如:
2.2.2 模糊匹配
我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,这样当错误码为4开头的时候就跳转到4xx页面,当错误码是5开头的就跳转到5xx页面。
例如:
注意:当error文件下有具体的错误码命名的文件的时候,当发生的错误码正号是该文件的名称,那么页面会跳转到该文件。
例如:当发生404错误的时候,页面跳转到404.html而不会跳转到4xx.html
2.2.3 错误页面可以获取的信息
1)timestamp:时间戳
2)status:状态码
3)error:错误提示
4)exception:异常对象
5)message:异常消息
6)errors:JSR303数据校验的错误都在这里
例如:
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<h1>status:[[${status}]]</h1>
<h2>timestamp:[[${timestamp}]]</h2>
</main>
2.3 没有模板引擎
在没有使用模板引擎的时候,当模板引擎找不到这个错误页面,就会去静态文件夹下查找。
例如:
注意:在static目录下的错误页面不能使用上面的错误信息对象。
如果以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面。
2.4 @ExceptionHandle 注解处理异常
该注解可以针对不同的异常,编写不同的异常处理方法
// 该方法需要返回一个 ModelAndView: 目的是可以让我们封装异常信息以及视图的指定
@ExceptionHandler(value = {java.lang.NullPointerException.class})
public ModelAndView nullExceptionHandler(Exception ex) {
ModelAndView mv = new ModelAndView();
mv.addObject("error", ex.toString());
mv.setViewName("error");
return mv;
}
我们知道在程序中,异常种类千奇百怪的,该方式虽然可以实现针对不同的异常进行不同的处理,但是同样给我们带来了很大的负担,也增加了很多冗余的代码。
注意:该方式处理异常只针对当前controller有效,不具备跨controller的能力。
那我们能不能将异常处理方法集中写在一个地方了?这样每个controller中都做异常处理很显然不是我们所希望的,答案是可以的,在boot中提供了注解@ControllerAdvice来实现这个功能
2.5 使用全局异常处理类——@ControllerAdvice
我们可以创建一个能够处理异常的全局异常类。 在该类上需要添加@ControllerAdvice 注解,配合@ExceptionHandler 注解处理异常,例如:
@ControllerAdvice
public class GloableException {
@ExceptionHandler(value = {java.lang.NullPointerException.class})
public ModelAndView nullExceptionHandler(Exception ex) {
ModelAndView mv = new ModelAndView();
mv.addObject("error", ex.toString());
mv.setViewName("error");
return mv;
}
}
在GloableException类上加上注解@ControllerAdvice,这样,当controller出现异常的时候,就会到该类去查找对应的异常处理逻辑,如果找到了,就按照既定的逻辑走,如果没有找到,就按照默认的逻辑处理。
2.6 SimpleMappingExceptionResolver
该方式处理异常其实就是对第三点,使用@ControllerAdvice标注全局异常处理类方式的简化。
因为boot在启动的时候,会对@configuration注解标记的类进行加载处理,所以,我们可以定义一个全局异常类,用注解@configuration表示,然后定义一个统一的异常处理方法,方法返回值为SimpleMappingExceptionResolver,方法名自定义即可,最后,在该方法上需要添加一个注解@Bean,该注解就是boot在启动时候,会对@Configuration进行加载,然后会扫描该类,方式方法上加了@Bean的,都会被springboot去执行。
例如:
/**
* 通过 SimpleMappingExceptionResolver 做全局异常处理
* * */
@Configuration
public class GlobalException {
/**
* 该方法必须要有返回值。 返回值类型必须是:
SimpleMappingExceptionResolver
*/
@Bean
public SimpleMappingExceptionResolver getSimpleMappingExceptionResolver(){
SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
Properties mappings = new Properties();
/**
* 参数一: 异常的类型, 注意必须是异常类型的全名
* 参数二: 视图名称
*/
mappings.put("java.lang.ArithmeticException", "error1");
mappings.put("java.lang.NullPointerException","error2");
//设置异常与视图映射信息的
resolver.setExceptionMappings(mappings);
return resolver;
}
}
2.7 自定义HandlerExceptionResolver
SimpleMappingExceptionResolver虽然简化了我们对异常的处理,但是它只能对异常进行一种异常类型和视图的映射而不能做视图跳转的时候,传递异常信息。
使用起来也很简单,只需要让我们的全局异常处理类视线HandlerExceptionResolver接口,然后写异常处理逻辑即可,例如:
/**
* 通过实现 HandlerExceptionResolver 接口做全局异常处理
* *
*/
@Configuration
public class GlobalException implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request,HttpServletResponse response, Object handler,Exception ex) {
ModelAndView mv = new ModelAndView();
//判断不同异常类型, 做不同视图跳转
if(ex instanceof ArithmeticException){
mv.setViewName("error1");
}
if(ex instanceof NullPointerException){
mv.setViewName("error2");
}
mv.addObject("error", ex.toString());
return mv;
}
}
2.8 使用@ControllerAdvice+@ExceptionHandler
例如;
package com.bjc.exception;
import java.util.HashMap;
import java.util.Map;
import org.aopalliance.intercept.Invocation;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice
public class MyException {
/**只要程序出现异常,都会进入该方法
* @param e
* @return
*/
@ResponseBody
@ExceptionHandler(value=java.lang.Exception.class)
public Map<String,Object> myException(Exception e){
Map<String,Object> map = new HashMap<String, Object>();
map.put("code", 500);
map.put("msg", "服务器异常!");
return map;
}
}
这样,只要程序产生了没被处理的异常信息,都会在这里做统一处理,但是,有时候,我们往往希望针对不同的异常做出的不同的反应,这样子的话,这种方式就不能满足我们的需求了,但是,幸运的是,我们可以在这里定义多个多个方法,来处理不同的异常。
例如:
package com.bjc.exception;
import java.util.HashMap;
import java.util.Map;
import org.aopalliance.intercept.Invocation;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice
public class MyException {
/**只要程序出现异常,都会进入该方法
* @param e
* @return
*/
@ResponseBody
@ExceptionHandler(value=java.lang.Exception.class)
public Map<String,Object> myException(Exception e){
Map<String,Object> map = new HashMap<String, Object>();
map.put("code", 500);
map.put("msg", "服务器异常!");
return map;
}
/**只要程序出现空指针异常,都会进入该方法
* @param e
* @return
*/
@ResponseBody
@ExceptionHandler(value=java.lang.NullPointerException.class)
public Map<String,Object> myNullException(Exception e){
Map<String,Object> map = new HashMap<String, Object>();
map.put("code", 600);
map.put("msg", "空指针异常!");
return map;
}
}
从这里可以看出,在全局异常处理中,对于异常的匹配是采取近亲原则来匹配的。
3. 定制错误json数据——异常处理器
3.1 方式一:自定义异常处理器——ExceptionHandler
该注解可以针对不同的异常,编写不同的异常处理方法
1)处理器
package com.bjc.exception;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Map;
@ControllerAdvice
public class MyExceptionHandler {
@ResponseBody
@ExceptionHandler(Exception.class)
public Map<String,Object> handlerException(Exception ex){
Map<String,Object> map = new HashMap<>();
map.put("code","出错了!");
map.put("msg", ex.getMessage());
return map;
}
}
2)controller模拟异常场景
@GetMapping("/hello")
public String hello(Model model){
try {
int a = 1/0;
} catch (Exception e){
throw new RuntimeException("除数不能为0!");
}
return "emp/list";
}
3)访问
该方式有一个缺陷,无法自适应,也就是不能让浏览器访问返回页面,其他客户端访问返回json数据的效果。
3.2 方式二:异常处理器使用转发达到自适应效果
3.2.1 异常处理器
@ControllerAdvice
public class MyExceptionHandler {
// @ResponseBody
@ExceptionHandler(Exception.class)
public String handlerException(Exception ex, HttpServletRequest request){
Map<String,Object> map = new HashMap<>();
map.put("code","出错了!");
map.put("msg", ex.getMessage());
// 设置状态码 key值为固定值
request.setAttribute("javax.servlet.error.status_code",500);
return "forward:/error";
}
}
注意:设置状态码的key值为固定值
3.2.2 页面
因为,异常处理器中默认设置的状态码就为500,所以只需要改5xx.html即可
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<h1>status:[[${status}]]</h1>
<h2>timestamp:[[${timestamp}]]</h2>
<h2>exception:[[${exception}]]</h2>
<h2>message:[[${message}]]</h2>
<h2>ext:[[${ext.code}]]</h2>
<h2>ext:[[${ext.message}]]</h2>
<h2>code:[[${code}]]</h2>
<h2>msg:[[${msg}]]</h2>
1111
</main>
运行效果:
我们发现,我们自己在异常处理器中自定义的数据并没有被返回。
3.3 方式三:携带自定义异常信息显示
出现错误以后,会来到/error请求,会被BasicErrorController处理,响应出去可以获取的数据是由getErrorAttributes得到的(是AbstractErrorController(ErrorController)规定的方法),而在boot中最终异常数据处理是在DefaultErrorAttributes.getErrorAttributes()中进行处理的,所以我们可以自定义一个ErrorAttributes类来添加我们的定制数据
3.3.1 异常处理器类
package com.bjc.exception;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@ControllerAdvice
public class MyExceptionHandler {
// @ResponseBody
@ExceptionHandler(Exception.class)
public String handlerException(Exception ex, HttpServletRequest request){
Map<String,Object> map = new HashMap<>();
map.put("code","出错了!");
map.put("msg", ex.getMessage());
// 设置状态码 key值为固定值
request.setAttribute("javax.servlet.error.status_code",500);
// 将异常信息放入request域中
request.setAttribute("ext",map);
return "forward:/error";
}
}
注意:这里比较重要的一步就是将自定义map放入request中。
3.3.2 自定义ErrorAttributes类
package com.bjc.commponent;
import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
Map<String, Object> map = super.getErrorAttributes(webRequest, options);
// 定制自己的错误信息
map.put("company","bjc");
// 获取异常处理器中携带的数据
Map<String, Object> ext = (Map<String, Object>) webRequest.getAttribute("ext", 0);
map.put("ext",ext);
return map;
}
}
注意:webRequest对request进行了封装。
3.3.3 页面
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<h1>status:[[${status}]]</h1>
<h2>timestamp:[[${timestamp}]]</h2>
<h2>exception:[[${exception}]]</h2>
<h2>message:[[${message}]]</h2>
<h2>ext.code:[[${ext.code}]]</h2>
<h2>ext.msg:[[${ext.msg}]]</h2>
</main>
运行结果: