一。springboot默认处理异常机制
默认效果:
1)、浏览器,返回一个默认的错误页面 (通过浏览器的请求头来返回页面)
2)、如果是其他客户端,默认响应一个json数据
原理:主要给日容器中注册了以下组件:
- ErrorPageCustomizer 系统出现错误以后来到error请求进行处理;相当于(web.xml注册的错误页面规则)
- BasicErrorController 处理/error请求
- DefaultErrorViewResolver 默认的错误视图解析器
- DefaultErrorAttributes 错误信息
- defaultErrorView 默认错误视图
原理“
1.系统一旦出现错误,ErrorPageCustomize发送error请求进行处理;,(就如以前在web.xml文件中配置错误页面一样)
注册错误页面
路径为:
2.BasicErrorController 就会来处理上面的error请求。 可以通过配置server.error.path=来配置自定义的error请求路径
其内部的方法这两个 一个是用于返回页面视图 一个是返回json数据,如何区分返回页面视图还是json数据 是通过header的头信息来区别的。
响应页面:通过resolveErrorView方法获取响应的页面,
从modelAndView = resolver.resolveErrorView(request, status, model);获取页面
3.那么这些组件哪儿来呢,就是DefaultErrorViewResolver 得来。
调用了resolveErrorView()方法。
在error/状态码下面通过模板引擎寻找对应的页面
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
//把状态码和model传过去获取视图
ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model);
//上面没有获取到视图就使用把状态吗替换再再找,以4开头的替换为4xx,5开头替换为5xx,见下文(如果定制错误响应)
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) {
//viewName传过来的是状态码,例:/error/404
String errorViewName = "error/" + viewName;
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
//模板引擎可以解析这个页面地址就用模板引擎解析
return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
}
4.异常页面可以获取哪些信息呢?可以使用哪些属性呢?
再看一下BasicErrorController的errorHtml方法
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = this.getStatus(request);
//model的数据
Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
return modelAndView != null ? modelAndView : new ModelAndView("error", model);
}
看一下调用的this.getErrorAttributes()方法
protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
WebRequest webRequest = new ServletWebRequest(request);
return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
}
再看 this.errorAttributes.getErrorAttributes()方法, this.errorAttributes是接口类型ErrorAttributes,实现类就一个DefaultErrorAttributes,看一下DefaultErrorAttributes的 getErrorAttributes()方法
protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
WebRequest webRequest = new ServletWebRequest(request);
return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
}
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;
}
如何定制错误页面:
1)、如何定制错误的页面;
1)、有模板引擎的情况下;error/状态码; 【将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的 error文件夹下】,发生此状态码的错误就会来到 对应的页面;
我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态 码.html);
页面能获取的信息;属性
timestamp:时间戳
status:状态码
error:错误提示
exception:异常对象
message:异常消息
errors:JSR303数据校验的错误都在这里**
2.0以后默认是不显示exception的,需要在配置文件中开启
server.error.include-exception=true
原因:
在注册时
2)、没有模板引擎(模板引擎找不到这个错误页面),静态资源文件夹下找;
静态资源文件夹下面就没法使用thymeleaf语法规则,则无法获取status这些属性
3)、以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面;
3.自定义异常json数据
1.创建自定义的异常类:
package com.wcy.springboot.exception;
public class MyUserException extends RuntimeException {
public MyUserException() {
super("用户名出错了");//自定义异常类
}
}
2.哪些地方捕获自定义异常:例子如下方username为123456则捕获异常
@GetMapping("/hello")
@ResponseBody
public String hello(String username){
if("123456".equals(username)){
throw new MyUserException();//当用户名为123456则处理自定义异常
}
return "测试";
}
3.自定义异常处理的控制类 有点像controller
package com.wcy.springboot.controller;
import com.wcy.springboot.exception.MyUserException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Map;
//专门用于处理异常的控制器
@ControllerAdvice
public class ExceptionHandler {
//当捕获到自己自定义的异常时触发
//这样不管事浏览器还是客户端访问都会返回json数据,显然是不符合
@org.springframework.web.bind.annotation.ExceptionHandler(MyUserException.class)
@ResponseBody
public Map<String,Object> handlerException(Exception e){
Map<String,Object> map=new HashMap<>();
map.put("code","not username");
map.put("msg","用户名没有找到");
return map;
}
}
- 显然上方的不满足我们的需求 我们需要让他自适应,浏览器异常访问我们自定义的页面,客服端访问显示json数据。
/**
* 1.可以通过请求转发 转发到error请求
* 2.这样就会被springboot的BasicErrorController处理 它内部就有两个自适应的方法
* 3.获取请求域中的状态码 我们需要自定义
* Integer statusCode = (Integer)request.getAttribute("javax.servlet.error.status_code");
* @param e
* @return
*/
@org.springframework.web.bind.annotation.ExceptionHandler(MyUserException.class)
public String handlerException(Exception e, HttpServletRequest request){
Map<String,Object> map=new HashMap<>();
map.put("code","not username");
map.put("msg","用户名没有找到");
//传入我们的状态码 这样才能返回页面 默认返回200是成功 没法返回页面
request.setAttribute("javax.servlet.error.status_code",500);
request.setAttribute("etx",map);
return "forward:/error";//转发到error请求
}
所以我们的ExceptionHandler 中应该这样写 转发给BasicErrorController 中/error来处理,他内部就有两个自适应方法。
但是会存在问题是:页面会显示springboot内部的异常页面:原因是他会自动获取请求域中的状态码:显示的是200,所以没法跳转到我们自定义页面;
视图方法中获取请求码是通过:"javax.servlet.error.status_code"的属性解决的
Integer statusCode = (Integer)request.getAttribute("javax.servlet.error.status_code");
虽然页面也可以访问了,但是我们没法把数据携带出去:
BasicErrorController的两个方法都是从getErrorAttributes()中获取参数的,getErrorAttributes()又是该AbstractErrorController类的方法。
- 方法一:完全来编写一个ErrorController的实现类【或者是编写AbstractErrorController的子类】,放在容器中;这样去重写那两个自适应方法就可以了,显然这样太复杂了,不推荐。
- return this.errorAttributes.getErrorAttribute的方法获取参数,
他是又ErrorMvcAutoConfiguration(自动装配)中的errorAttributes方法来的,所以我们可以自定义组件 来继承DefaultErrorAttributes 类然后再重写方法就可以了
@Bean
@ConditionalOnMissingBean(
value = {ErrorAttributes.class},
search = SearchStrategy.CURRENT
)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException());
}
自定义组件:
package com.wcy.springboot.component;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;
import java.util.Map;
//是一个组件 让springboot识别这个类
@Component
public class MyErrorAttribute extends DefaultErrorAttributes {
//接受页面和json数据的属性
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
//获取自定义异常处理类传过来的数据
Map<String, Object> etx= (Map<String, Object>) webRequest.getAttribute("etx", 0);
Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
map.put("company","wcy");//自定义公司名称
map.put("msg",etx);
return map;
}
}
获取自定义json异常数据总结:
1.自定义异常类
package com.wcy.springboot.exception;
public class MyUserException extends RuntimeException {
public MyUserException() {
super("用户名出错了");//自定义异常类
}
}
2.自定义异常处理类
package com.wcy.springboot.controller;
import com.wcy.springboot.exception.MyUserException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
//专门用于处理异常的控制器
@ControllerAdvice
public class ExceptionHandler {
/* //当捕获到自己自定义的异常时触发
@org.springframework.web.bind.annotation.ExceptionHandler(MyUserException.class)
@ResponseBody
public Map<String,Object> handlerException(Exception e){
Map<String,Object> map=new HashMap<>();
map.put("code","not username");
map.put("msg","用户名没有找到");
return map;
}*/
/**
* 1.可以通过请求转发 转发到error请求
* 2.这样就会被springboot的BasicErrorController处理 它内部就有两个自适应的方法
* 3.获取请求域中的状态码 我们需要自定义
* Integer statusCode = (Integer)request.getAttribute("javax.servlet.error.status_code");
* @param e
* @return
*/
@org.springframework.web.bind.annotation.ExceptionHandler(MyUserException.class)
public String handlerException(Exception e, HttpServletRequest request){
Map<String,Object> map=new HashMap<>();
map.put("code","not username");
map.put("msg","用户名没有找到");
//传入我们的状态码 这样才能返回页面 默认返回200是成功 没法返回页面
request.setAttribute("javax.servlet.error.status_code",500);
request.setAttribute("etx",map);
return "forward:/error";//转发到error请求
}
}
3.自定义组件。
package com.wcy.springboot.component;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;
import java.util.Map;
//是一个组件 让springboot识别这个类
@Component
public class MyErrorAttribute extends DefaultErrorAttributes {
//接受页面和json数据的属性
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
//获取自定义异常处理类传过来的数据
Map<String, Object> etx= (Map<String, Object>) webRequest.getAttribute("etx", 0);
Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
map.put("company","wcy");//自定义公司名称
map.put("msg",etx);
return map;
}
}