一、错误处理机制
1)、SpringBooot默认的错误处理机制
默认效果:
1)、返回一个默认的错误页面
2)、如果是其他客户端,默认响应一个json数据
浏览器发送请求的请求头:text/html
其他客户端发送请求的请求头: /*
原理:
可以参照ErrorMvcAutoConfiguration;自动配置原理;
给容器中添加了以下组件:
1、DefaultErrorAttributes
//帮我们在页面共享信息;
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap();
errorAttributes.put("timestamp", new Date());
this.addStatus(errorAttributes, webRequest);
this.addErrorDetails(errorAttributes, webRequest, includeStackTrace);
this.addPath(errorAttributes, webRequest);
return errorAttributes;
}
2、BasicErrorController:处理error请求
@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class BasicErrorController extends AbstractErrorController {
private final ErrorProperties errorProperties;
public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) {
this(errorAttributes, errorProperties, Collections.emptyList());
}
//这个产生html类型的数据,浏览器发送的请求通过这个方法进行处理
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = this.getStatus(request);
Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
//ModelAndView是要去哪个页面作为错误页面--->ModeAndView是页面地址+页面内容
//这里去拿resolve的解析器对modelView进行解析
ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
return modelAndView != null ? modelAndView : new ModelAndView("error", model);
}
//这个产生的是json数据,其他客户端发送的请求通过这个方法进行处理
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
HttpStatus status = this.getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity(status);
} else {
Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
return new ResponseEntity(body, status);
}
}
3、ErrorPageCustomizer
@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
4、DefaultErrorViewResolver
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model);
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}
private ModelAndView resolve(String viewName, Map<String, Object> model) {
//默认springboot可以去找到某个页面--->error/404
String errorViewName = "error/" + viewName;
//模版引擎可以解析这个页面地址就用模版引擎解析
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
//如果模版引擎可以用的情况下就返回到errorViewName指定的视图地址。
//反之就在静态资源文件夹下找到errorViewName对应的页面 error/404.html
return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
}
步骤:
一但系统出现4xx或者5xx之类的错误,ErrorPageCustomizer就会生效(定制错误的响应规则),就会来到/error请求,随后被BasicErrorController处理。
1)、响应页面; 去哪个页面是由DefaultErrorViewResolver解析出来的,这个DefaultErrorViewResolver又是我们添加到容器中的组件,所以只需要去找到这个组件即可;
2)、如何定制错误响应:
1)、如何定制错误响应的页面:
1.有模版引擎的情况下:error/状态码【将错误页面命名为 错误状态码.html】,发生此状态码的错误就会来到对应的错误页面。比如error/404.html;我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态码.html)。
页面能获取的信息:
(1)timestamp:时间戳
(2)status:状态码
(3)error:错误提示
(4)exception:异常对象
(5)message:异常消息
(6)JSR303数据校验信息
2.没有模版引擎的情况下:自定义的404xx页面也可以放在静态static文件夹下,这样一样可以显示自定义错误页面,但是static下的页面无法加载动态信息。
3.以上都没有错误页面,就是默认来到SpringBoot默认的错误error页面。
2)、如何定制错误的json数据:
1、创建一个MyExceptionHandler.class
@ControllerAdvice
public class MyExceptionHandler {
//自定义处理异常
@ResponseBody
@ExceptionHandler(UserPrincipalNotFoundException.class)
public Map<String, Object> handleException(Exception e){
Map<String,Object> map=new HashMap<>();
map.put("code:","user.PrincipalNotFound");
map.put("message:",e.toString());
return map;
}
}
在这个自定义异常的类中,我们可以指定不同类别的Exception错误所返回的json数据列表,我们这里用的是Map,SpringBoot会自动把Map转为Json。
但是这个没有自适应效果:浏览器返回页面,客户端返回json。
我们需要把这个改为自适应情况。
2、自适应浏览器和客户端
@ExceptionHandler(UserPrincipalNotFoundException.class)
public String handleException(Exception e) {
Map<String, Object> map = new HashMap<>();
map.put("code:", "user.PrincipalNotFound");
map.put("message:", e.toString());
return "forward:/error";
}
我们只需要直接转发到forward:/error适配器就可以了。但是一定要传入状态码。
Integer statusCode = (Integer)request.getAttribute("javax.servlet.error.status_code");
这个是BasicErrorController源码中获取状态码的代码。所以我们就要在map中设置javax.servlet.error.status_code这个值。
@ExceptionHandler(UserPrincipalNotFoundException.class)
public String handleException(Exception e, HttpServletRequest request) {
//(Integer)request.getAttribute("javax.servlet.error.status_code");
//设置自己的状态码
request.setAttribute("javax.servlet.error.status_code",500);
Map<String, Object> map = new HashMap<>();
map.put("code:", "user.PrincipalNotFound");
map.put("h1:","aaaa");
map.put("message:", e.toString());
//我们需要把map放到request中,才可以带走
return "forward:/error";
}
但是 我们发现,我们自己自定义的map错误提示没有传到前端页面去,也没有传入到json中,仔细观察可以看出,我们没有把map传回自定义的错误机制中。
我们现在就要把自定义的数据传出去:
我们来看一下我们自定义的过程:
出错—>自定义错误变量–>/error请求–>BasicErrorController处理(这个地方做了自适应效果{页面、客户端})
响应出去可以获取的数据是由getErrorAttributis得到的(是AbstractErrorController(就是ErrorController)规定的方法);
1、方法一:
那么我们就可以完全来编写一个ErrorController的实现类【或者是编写AbstractErrorController的子类】,放在容器中;
2、方法二:
页面上【json返回】的数据都是通过this.errorAttributes.getErrorAttributes得到的;这就是一个
private final ErrorAttributes errorAttributes;
然后我们来看这个的自动配置情况:
@Bean
@ConditionalOnMissingBean(
value = {ErrorAttributes.class},
search = SearchStrategy.CURRENT
)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException());
}
我们 可以看到,如果在容器中没ErrorAttributes.class这个组件,就new一个新的默认ErrorAttrIbutes对象–>DefaultErrorAttributes;并且调用的是DefaultErrorAttribute.getErrorAttributes函数:
我们可以再来看看这个函数的实现:
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap();
errorAttributes.put("timestamp", new Date());
this.addStatus(errorAttributes, webRequest);
this.addErrorDetails(errorAttributes, webRequest, includeStackTrace);
this.addPath(errorAttributes, webRequest);
return errorAttributes;
}
我们就可以自己来实现一个getErrorAttributes这个函数,就可以传入数据了。
自定义ErrorAttributies:
//给容器中加入我们自己定义的错误属性
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
//调用父方法
Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
map.put("company","ogj");
return map;
}
这样我们就可以访问到我们设置的属性了:
但是我们还想把Exception的信息也传过来,那么我们就可以使用resquest来存取:
@ExceptionHandler(UserPrincipalNotFoundException.class)
public String handleException(Exception e, HttpServletRequest request) {
//(Integer)request.getAttribute("javax.servlet.error.status_code");
//设置自己的状态码
request.setAttribute("javax.servlet.error.status_code",500);
Map<String, Object> map = new HashMap<>();
map.put("code:", "user.PrincipalNotFound");
map.put("h1:","aaaa");
map.put("message:", e.toString());
//我们需要把map放到request中,才可以带走
request.setAttribute("ext",map);
return "forward:/error";
}
//返回值的map就是页面和json能获取的字段
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
//调用父方法
Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
map.put("company","ogj");
//WebRequest就是一个RequestAttribute
//0是request 1是session
Map<String,Object> ext = (Map<String, Object>) webRequest.getAttribute("ext", 0);
map.put("ext",ext);
return map;
}
这样我们就可以获取到我们设置的自定义错误了。
最终效果:错误处理是可以自适应的,可以通过定制ErrorAttributes改变需要返回的内容。