配置错误处理路径
@ConfigurationProperties(
prefix = "server",
ignoreUnknownFields = true
)
public class ServerProperties implements EmbeddedServletContainerCustomizer, EnvironmentAware, Ordered {
//错误配置参数
@NestedConfigurationProperty
private ErrorProperties error = new ErrorProperties();
}
springboot的参数配置类当中有一个错误参数配置类.
public class ErrorProperties {
@Value("${error.path:/error}")
private String path = "/error";
private ErrorProperties.IncludeStacktrace includeStacktrace;
这里配置了错误处理路径,默认的错误处理路径是/error,我们可以通过在配置文件当中配置server.error.path进行修改
错误自动配置类ErrorMvcAutoConfiguration
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({Servlet.class, DispatcherServlet.class})
@AutoConfigureBefore({WebMvcAutoConfiguration.class})
@EnableConfigurationProperties({ResourceProperties.class})
public class ErrorMvcAutoConfiguration {
private final ServerProperties serverProperties;
private final List<ErrorViewResolver> errorViewResolvers;
public ErrorMvcAutoConfiguration(ServerProperties serverProperties, ObjectProvider<List<ErrorViewResolver>> errorViewResolversProvider) {
this.serverProperties = serverProperties;
this.errorViewResolvers = (List)errorViewResolversProvider.getIfAvailable();
}
这里将springboot参数配置类ServerProperties 注入,同时也注入了ErrorProperties 。
ErrorMvcAutoConfiguration 中注入了ErrorPageCustomizer对错误链接进行注册。
@Bean
public ErrorMvcAutoConfiguration.ErrorPageCustomizer errorPageCustomizer() {
return new ErrorMvcAutoConfiguration.ErrorPageCustomizer(this.serverProperties);
}
private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {
private final ServerProperties properties;
protected ErrorPageCustomizer(ServerProperties properties) {
this.properties = properties;
}
public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
//注册/error或者配置文件中的server.error.path值
ErrorPage errorPage = new ErrorPage(this.properties.getServletPrefix() + this.properties.getError().getPath());
errorPageRegistry.addErrorPages(new ErrorPage[]{errorPage});
}
public int getOrder() {
return 0;
}
}
ErrorMvcAutoConfiguration 向容器中注入了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());
}
public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties, List<ErrorViewResolver> errorViewResolvers) {
super(errorAttributes, errorViewResolvers);
Assert.notNull(errorProperties, "ErrorProperties must not be null");
this.errorProperties = errorProperties;
}
public String getErrorPath() {
return this.errorProperties.getPath();
}
@RequestMapping(
produces = {"text/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 modelAndView = this.resolveErrorView(request, response, status, model);
return modelAndView == null ? new ModelAndView("error", model) : modelAndView;
}
//对其他客户端的错误请求进行处理
@RequestMapping
@ResponseBody
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = this.getStatus(request);
return new ResponseEntity(body, status);
}
errorHtml
是对网页端错误请求的处理,error
是对其他客户端错误请求的处理
对网页端错误请求的处理
@RequestMapping(
produces = {"text/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 modelAndView = this.resolveErrorView(request, response, status, model);
//如果没有获取视图解析器,就用在ErrorMvcAutoConfiguration中注入的defaultErrorView
return modelAndView == null ? new ModelAndView("error", model) : modelAndView;
}
对错误信息的处理
this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)))
protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
RequestAttributes requestAttributes = new ServletRequestAttributes(request);
return this.errorAttributes.getErrorAttributes(requestAttributes, includeStackTrace);
}
这里的RequestAttributes
就是在ErrorMvcAutoConfiguration
中注入容器的DefaultErrorAttributes
//ErrorMvcAutoConfiguration 代码
@Bean
@ConditionalOnMissingBean(
value = {ErrorAttributes.class},
search = SearchStrategy.CURRENT
)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}
DefaultErrorAttributes
中的getErrorAttributes
方法
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap();
errorAttributes.put("timestamp", new Date());//放入timestamp
this.addStatus(errorAttributes, requestAttributes);//放入status
this.addErrorDetails(errorAttributes, requestAttributes, includeStackTrace);//放入exception和message
this.addPath(errorAttributes, requestAttributes);//放入path
return errorAttributes;
}
将上面的分析,我们能在页面展示的信息有时间戳(timestamp),错误状态码(status),异常对象(exception),异常消息(message),错误路径(path)
获取视图解析器
ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
Iterator var5 = this.errorViewResolvers.iterator();
ModelAndView modelAndView;
do {
if (!var5.hasNext()) {
return null;
}
ErrorViewResolver resolver = (ErrorViewResolver)var5.next();
modelAndView = resolver.resolveErrorView(request, status, model);
} while(modelAndView == null);
return modelAndView;
}
ErrorViewResolver
就只在ErrorMvcAutoConfiguration
中注入容器的DefaultErrorViewResolver
@Bean
@ConditionalOnBean({DispatcherServlet.class})
@ConditionalOnMissingBean
public DefaultErrorViewResolver conventionErrorViewResolver() {
return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties);
}
modelAndView = resolver.resolveErrorView(request, status, model);
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
ModelAndView modelAndView = this.resolve(String.valueOf(status), 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) {
String errorViewName = "error/" + viewName;// error/错误状态码 error/400
//如果有模板引擎,就在模板引擎中去找 error/错误状态码 页面
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
//如果模板引擎当中没有找到相应的页面,再去静态资源下找,如果还没有返回null
return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
}
private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
String[] var3 = this.resourceProperties.getStaticLocations();
int var4 = var3.length;
for(int var5 = 0; var5 < var4; ++var5) {
String location = var3[var5];
try {
Resource resource = this.applicationContext.getResource(location);
resource = resource.createRelative(viewName + ".html");
if (resource.exists()) {
return new ModelAndView(new DefaultErrorViewResolver.HtmlResourceView(resource), model);
}
} catch (Exception var8) {
}
}
return null;
}
如果没有获取视图解析器,就用在ErrorMvcAutoConfiguration中注入的defaultErrorView。
private final ErrorMvcAutoConfiguration.SpelView defaultErrorView = new ErrorMvcAutoConfiguration.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>");
protected WhitelabelErrorViewConfiguration() {
}
@Bean(
name = {"error"}
)
@ConditionalOnMissingBean(
name = {"error"}
)
public View defaultErrorView() {
return this.defaultErrorView;
}
我们如何定义自己的错误页面
有模板引擎的话,
在模板中配置error/4xx或者error/5xx页面(页面后缀根据引擎来写),
没有模板引擎
在静态资源下配置error/4xx或者error/5xx页面
以上都没有
以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面;
对上述原理的实际用途,自定义错误json数据
自定义异常处理增强
@ControllerAdvice
public class HandlerException {
//@ExceptionHandler标注对那些错误进行增强,如果同一处理可以写成Exception.class,如果想对错误单独处理,配置自己的错误
@ExceptionHandler(Exception.class)
public String handler(Exception e, HttpServletRequest request){
Map<String,Object> map = new HashMap<>();
map.put("code","1001");
map.put("msg","错误了");
request.setAttribute("javax.servlet.error.status_code",500);
request.setAttribute("ext",map);
e.printStackTrace();
return "forward:/error";
}
//如果Exception和BindException都进行不同的处理,那么如果发生BindException,优先有此方法进行处理,其他异常由handler方法处理
@ExceptionHandler(BindException.class)
public String bindExceptionHandler(Exception e, HttpServletRequest request){
Map<String,Object> map = new HashMap<>();
map.put("code","1002");
map.put("msg","错误了1002");
//设置状态码。是为了让springboot自动识别赚到相应页面
request.setAttribute("javax.servlet.error.status_code",500);
request.setAttribute("ext",map);
e.printStackTrace();
//使用请求转发是为了利用springboot为我们自动识别是网页还是其他客户端
return "forward:/error";
}
}
将自定义的异常信息加入加入springboot返回页面
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
//获取springboot定义的异常数据
Map<String, Object> map = super.getErrorAttributes(requestAttributes, includeStackTrace);
//获取我们自己定义的异常数据
Map<String, Object> ext = (Map<String, Object>) requestAttributes.getAttribute("ext", 0);
//将我们的自己定义的异常数据放到map中返回
map.put("ext",ext);
return map;
}
}
这是利用自动配置当中对DefaultErrorAttributes注入条件
@Bean
//只有当容器中没有ErrorAttributes类型的bean是,才会将DefaultErrorAttributes注入容器,上面我们定义了自己的MyErrorAttributes继承于DefaultErrorAttributes,病注入容器当中,所以此时DefaultErrorAttributes将不在注入到容器中。进行错误信息处理时会用我们自己编写的MyErrorAttributes
@ConditionalOnMissingBean(
value = {ErrorAttributes.class},
search = SearchStrategy.CURRENT
)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}
只有当容器中没有ErrorAttributes类型的bean是,才会将DefaultErrorAttributes注入容器,上面我们定义了自己的MyErrorAttributes继承于DefaultErrorAttributes,并注入容器当中,所以此时DefaultErrorAttributes将不在注入到容器中。进行错误信息处理时会用我们自己编写的MyErrorAttributes