1,大致流程
一但系统出现4xx或者5xx之类的错误;ErrorPageCustomizer就会生效(定制错误的响应规则);就会来到/error请求;就会被BasicErrorController处理;
响应页面;去哪个页面是由DefaultErrorViewResolver解析得到的;
2,错误自动配置类
ErrorMvcAutoConfiguration ,此类帮我们自动配置了:
@Bean
public ErrorPageCustomizer errorPageCustomizer() {
return new ErrorPageCustomizer(this.serverProperties);
}
private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {
/*添加错误映射,即发生错误时会发送 /error 请求*/
@Override
public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
ErrorPage errorPage = new ErrorPage(
this.properties.getServlet().getServletPrefix()
+ this.properties.getError().getPath());
errorPageRegistry.addErrorPages(errorPage);
}
}
public class ErrorProperties {
@Value("${error.path:/error}")
private String path = "/error";
3,BasicErrorController 处理 /error 请求
/*返回页面响应*/
@RequestMapping(produces = "text/html")
public ModelAndView errorHtml(HttpServletRequest request,
HttpServletResponse response) {
/*得到错误响应码*/
HttpStatus status = getStatus(request);
/*得到错误要显示的响应数据*/
Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
/*如果没有找到对应的 /error/5xx.html 视图 就会返回一个名为 error 的ModelAndView*/
return (modelAndView != null ? modelAndView : new ModelAndView("error", model));
}
/*返回json响应*/
@RequestMapping
@ResponseBody
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = getErrorAttributes(request,
isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
/*将响应状态码和响应数据封装成ResponseEntity 返回*/
return new ResponseEntity<>(body, status);
}
①,getStatus
protected HttpStatus getStatus(HttpServletRequest request) {
/*从request 获取key为 javax.servlet.error.status_code 的值*/
Integer statusCode = (Integer) request
.getAttribute("javax.servlet.error.status_code");
if (statusCode == null) {
/*获取不到就响应一个500*/
return HttpStatus.INTERNAL_SERVER_ERROR;
}
try {
return HttpStatus.valueOf(statusCode);
}
catch (Exception ex) {
/*发生错误也响应一个500*/
return HttpStatus.INTERNAL_SERVER_ERROR;
}
}
②,getErrorAttributes 获取错误响应数据
public abstract class AbstractErrorController implements ErrorController {
private final ErrorAttributes errorAttributes;
protected Map<String, Object> getErrorAttributes(HttpServletRequest request,
boolean includeStackTrace) {
WebRequest webRequest = new ServletWebRequest(request);
/*就是调用ErrorAttributes 里的 getErrorAttributes 方法获取响应数据*/
return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
}
}
public class ErrorMvcAutoConfiguration {
/*为我们注册了一个DefaultErrorAttributes */
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes(
this.serverProperties.getError().isIncludeException());
}
}
//即调用了DefaultErrorAttributes 的 getErrorAttributes 方法,如下:
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest,
boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap<>();
errorAttributes.put("timestamp", new Date());
addStatus(errorAttributes, webRequest);
addErrorDetails(errorAttributes, webRequest, includeStackTrace);
addPath(errorAttributes, webRequest);
return errorAttributes;
}
/*
总的来说就是放了如下数据
页面能获取的信息;
timestamp:时间戳
status:状态码
error:错误提示
exception:异常对象
message:异常消息
errors:JSR303数据校验的错误都在这里
*/
③,name为error 的ModelAndView
public class ErrorMvcAutoConfiguration {
@Configuration
@ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", matchIfMissing = true)
@Conditional(ErrorTemplateMissingCondition.class)
protected static class WhitelabelErrorViewConfiguration {
//这个即我们经常看到的错误页
private final SpelView defaultErrorView = new SpelView(
"<html><body><h1>Whitelabel Error Page</h1>"
+ "<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>"
+ "<div id='created'>${timestamp}</div>"
+ "<div>There was an unexpected error (type=${error}, status=${status}).</div>"
+ "<div>${message}</div></body></html>");
// 即是这个View
@Bean(name = "error")
@ConditionalOnMissingBean(name = "error")
public View defaultErrorView() {
return this.defaultErrorView;
}
}
4,定制错误响应
①,编写一个错误处理类,这里还是用到了@ControllerAdvice 注解
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
//声明是一个错误处理器
@ControllerAdvice
public class ErrorControllAdvice {
// 处理那种类型的错误
@ExceptionHandler(Exception.class)
public String errorHandle(HttpServletRequest request){
// 添加额外错误响应数据
Map<String,Object> map=new HashMap<>();
// 添加一个公司的响应数据
map.put("company","小米");
// 放到request 中,然后在我们定制的DefaultErrorAttributes 里获取
request.setAttribute("ext",map);
// 设置响应状态码
request.setAttribute("javax.servlet.error.status_code",500);
// 转发到/error 请求,这样就能根据客户端优化接收何种类型数据,决定响应html还是json
return "forward:/error";
}
}
②,定制DefaultErrorAttributes
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.WebRequest;
import java.util.Map;
@Component
public class MyDefaultErrorAttributes extends DefaultErrorAttributes {
/*重写getErrorAttributes 方法*/
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
// 获取原来的响应数据
Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
//从请求域中拿值
Map<String, Object> ext = (Map<String, Object>) webRequest.getAttribute("ext", RequestAttributes.SCOPE_REQUEST);
//添加我们定制的响应数据
map.put("ext",ext);
// 返回带有我们定制的数据的map
return map;
}
}