springboot错误页面原理
以下会提到的重点类:
-
BasicErrorController 基础错误控制器
-
DefaultErrorViewResolver 默认错误视图解析器
-
DefaultErrorAttributes 默认错误属性类
-
ErrorPageCustomizer 错误页面定制器
ErrorMvcAutoConfiguration 错误mvc自动配置类
package org.springframework.boot.autoconfigure.web.servlet.error;
/**
* {@link EnableAutoConfiguration Auto-configuration} 通过MVC错误控制器呈现错误
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({
Servlet.class, DispatcherServlet.class })
// 在主WebMvcAutoConfiguration之前加载,以便可以使用错误视图
@AutoConfigureBefore(WebMvcAutoConfiguration.class)
@EnableConfigurationProperties({
ServerProperties.class, ResourceProperties.class, WebMvcProperties.class })
public class ErrorMvcAutoConfiguration {
// Web服务器的{@link ConfigurationProperties @ConfigurationProperties}(例如端口和路径设置)。
private final ServerProperties serverProperties;
// 构造器,需要传入ServerProperties
public ErrorMvcAutoConfiguration(ServerProperties serverProperties) {
this.serverProperties = serverProperties;
}
// 默认错误属性类。ErrorAttributes的默认实现。为错误视图提供错误信息等属性。
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}
/*
基本的全局错误Controller,呈现ErrorAttributes(在这里指DefaultErrorAttributes)。可以使用Spring MVC抽象(例如@ExceptionHandler)或添加Servlet(AbstractServletWebServerFactory#setErrorPages服务器错误页面)来处理更具体的错误。
*/
@Bean
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes,
ObjectProvider<ErrorViewResolver> errorViewResolvers) {
return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
errorViewResolvers.orderedStream().collect(Collectors.toList()));
}
// WebServerFactoryCustomizer,用于配置服务器的错误页面。
@Bean
public ErrorPageCustomizer errorPageCustomizer(DispatcherServletPath dispatcherServletPath) {
return new ErrorPageCustomizer(this.serverProperties, dispatcherServletPath);
}
// BeanFactoryPostProcessor,以确保在使用AOP时保留ErrorController MVC bean的目标类。
@Bean
public static PreserveErrorControllerTargetClassPostProcessor preserveErrorControllerTargetClassPostProcessor() {
return new PreserveErrorControllerTargetClassPostProcessor();
}
// 默认错误视图解析器配置
@Configuration(proxyBeanMethods = false)
static class DefaultErrorViewResolverConfiguration {
// ioc容器
private final ApplicationContext applicationContext;
private final ResourceProperties resourceProperties;
// 构造器
DefaultErrorViewResolverConfiguration(ApplicationContext applicationContext,
ResourceProperties resourceProperties) {
this.applicationContext = applicationContext;
this.resourceProperties = resourceProperties;
}
// 创建错误视图解析器
@Bean
@ConditionalOnBean(DispatcherServlet.class)
@ConditionalOnMissingBean(ErrorViewResolver.class)
DefaultErrorViewResolver conventionErrorViewResolver() {
return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties);
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", matchIfMissing = true)
@Conditional(ErrorTemplateMissingCondition.class)
protected static class WhitelabelErrorViewConfiguration {
private final StaticView defaultErrorView = new StaticView();
@Bean(name = "error")
@ConditionalOnMissingBean(name = "error")
public View defaultErrorView() {
return this.defaultErrorView;
}
// 如果用户添加@EnableWebMvc,则WebMvcAutoConfiguration中的bean名称视图解析器将消失,因此请重新添加它以避免失效。
@Bean
@ConditionalOnMissingBean
public BeanNameViewResolver beanNameViewResolver() {
BeanNameViewResolver resolver = new BeanNameViewResolver();
resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
return resolver;
}
}
/**
* SpringBootCondition 在未检测到错误模板视图时匹配
* 缺少错误模板类
*/
private static class ErrorTemplateMissingCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage.forCondition("ErrorTemplate Missing");
TemplateAvailabilityProviders providers = new TemplateAvailabilityProviders(context.getClassLoader());
TemplateAvailabilityProvider provider = providers.getProvider("error", context.getEnvironment(),
context.getClassLoader(), context.getResourceLoader());
if (provider != null) {
return ConditionOutcome.noMatch(message.foundExactly("template from " + provider));
}
return ConditionOutcome.match(message.didNotFind("error template view").atAll());
}
}
/**
* 简单的View(视图)实现,可编写默认的HTML错误页面。
* 如果没有找到错误模板视图、静态html的话,就会调用这个类,生成一段错误文本返回。
*/
private static class StaticView implements View {
private static final MediaType TEXT_HTML_UTF8 = new MediaType("text", "html", StandardCharsets.UTF_8);
private static final Log logger = LogFactory.getLog(StaticView.class);
/*
在DispatcherServlet类中的render()方法中调用view.render(mv.getModelInternal(), request, response),然后跳转到这里。
渲染简易文本错误视图。
*/
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception {
if (response.isCommitted()) {
String message = getMessage(model);
logger.error(message);
return;
}
// 设置ContentType返回头
response.setContentType(TEXT_HTML_UTF8.toString());
StringBuilder builder = new StringBuilder();
// 时间戳
Date timestamp = (Date) model.get("timestamp");
// 错误消息
Object message = model.get("message");
Object trace = model.get("trace");
if (response.getContentType() == null) {
response.setContentType(getContentType());
}
builder.append("<html><body><h1>Whitelabel Error Page</h1>").append(
"<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>")
.append("<div id='created'>").append(timestamp).append("</div>")