Spring Boot 自动配置 ErrorMvcAutoConfiguration

本文介绍Spring Boot中自动配置的ErrorMvcAutoConfiguration类如何为基于SpringMVC的Servlet Web环境提供错误信息渲染。该配置包括默认错误属性、基本错误控制器、错误页面定制器等组件,以及默认错误视图解析器和Whitelabel错误视图。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述

自动配置ErrorMvcAutoConfiguration针对基于Spring MVCServlet Web环境配置了一个MVC error控制类用于渲染错误信息页面。

该配置类通过注解声明在如下条件满足时生效 :

  1. 必须是一个Servlet Web应用;
  2. Servlet,DispatcherServlet必须存在于classpath;

    这一声明要求必须是基于Spring MVCServlet Web应用;

另外该配置类通过注解声明自己的运行时机为 :

  1. WebMvcAutoConfiguration自动配置运行之后;

该配置类的具体运行效果如下 :

  1. 定义 bean DefaultErrorAttributes errorAttributes

    在该bean不存在时才定义。
    bean基于配置属性被创建,然后被bean basicErrorController使用,用于从处理出错的用户请求中获取错误信息然后渲染到页面。

  2. 定义 bean BasicErrorController basicErrorController

    在该bean不存在时才定义。
    映射到URL路径${server.error.path:${error.path:/error}}用于处理用户请求出错时展示错误页面,内部会使用bean errorAttributes从遇到错误的用户请求中分析错误信息然后展示到页面。

  3. 定义 bean ErrorPageCustomizer errorPageCustomizer
    1. bean的一个主要特征是实现了接口ErrorPageRegistrar,因此它会被ErrorPageRegistrarBeanPostProcessor用于往ErrorPageRegistry注册错误页面。该bean所提供的注册页面缺省是一个URL路径为/errorErrorPage对象。而这里ErrorPageRegistry时当前Servlet容器对象的的工厂,在Servlet容器是Tomcat的情况下,它其实就是TomcatServletWebServerFactory
    2. 关于ErrorPageRegistrarBeanPostProcessor,可以参考ServletWebServerFactoryAutoConfiguration
  4. 定义 bean PreserveErrorControllerTargetClassPostProcessor preserveErrorControllerTargetClassPostProcessor

    bean通过一个类静态方法定义,是一个BeanFactoryPostProcessor,它的目的是为ErrorController bean的定义设置属性preserveTargetClass:true,从而确保ErrorController bean如果有代理对象的话代理对象总是代理在类上而不是接口上。

  5. 内置配置类DefaultErrorViewResolverConfiguration
    1. 定义 bean DefaultErrorViewResolver conventionErrorViewResolver

      仅在该类型bean不存在,并且DispatcherServlet bean存在的情况下才定义该bean
      ViewResolver 会基于一些常见约定解析错误视图,比如尝试根据HTTP错误状态码解析出错误处理视图。
      它会在目录/error下针对提供的HTTP错误状态码搜索模板或者静态资源,比如,给定了HTTP状态码404,
      它会尝试搜索如下模板或者静态资源:
      /<templates>/error/404.<ext> – 这里<templates>表示所配置的模板所在目录,<ext>表示所用的模板的文件名
      /<static>/error/404.html – 这里<static>表示静态资源文件所在路径
      /<templates>/error/4xx.<ext>
      /<static>/error/4xx.html

  6. 内置配置类WhitelabelErrorViewConfiguration

    这里是著名的Spring Whitelable Error被定义的地方。

    1. 定义 bean View defaultErrorView,指定bean名称为error

      定义一个bean,使用名称error,这是一个视图bean,该视图是一个HTML生成模板,利用一个错误信息Model生成一个HTML错误页面。它使用的错误信息Model其实就是basicErrorController利用errorAttributes从遇到错误的用户请求中获取的错误信息。
      bean仅在不存在名称为errorbean时才定义。

    2. 定义 bean BeanNameViewResolver beanNameViewResolver

      bean仅在不存在同类型的bean时才定义。
      这样一个根据bean名称解析视图的视图解析器bean,用来确保上面定义的视图bean error可以被解析到。

另外,该自动配置类通过注解@EnableConfigurationProperties确保存在对应相应配置参数的bean :

bean名称bean类型对应配置项前缀
server-org.springframework.boot.autoconfigure.web.ServerPropertiesServerPropertiesserver
spring.resources-org.springframework.boot.autoconfigure.web.ResourcePropertiesResourcePropertiesspring.resources
spring.mvc-org.springframework.boot.autoconfigure.web.servlet.WebMvcPropertiesWebMvcPropertiesspring.mvc

注意 : 以上这些bean有可能在其他自动配置类中通过@EnableConfigurationProperties被定义,根据@EnableConfigurationProperties的工作逻辑,重复定义的bean仅会在首次被处理时被注册到容器。具体的工作原理可以参考类EnableConfigurationPropertiesImportSelector的实现。

源代码

源代码版本 : spring-boot-autoconfigure-2.1.3.RELEASE

package org.springframework.boot.autoconfigure.web.servlet.error;

// 省略 import 行

/**
 * EnableAutoConfiguration Auto-configuration to render errors via an MVC error
 * controller.
 *
 */
@Configuration
// 仅在 Servlet Web 应用环境下生效
@ConditionalOnWebApplication(type = Type.SERVLET)
// 仅在 Servlet,DispatcherServlet 类存在时生效,也就是必须是使用了 Spring MVC 时才生效,
// 如果使用的是其他非 Spring MVC 机制,则不生效
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
// Load before the main WebMvcAutoConfiguration so that the error View is available
// 在自动配置 WebMvcAutoConfiguration 之前执行
@AutoConfigureBefore(WebMvcAutoConfiguration.class)
// 确保如下前缀的属性被加载到相应的 bean :
// server.* => ServerProperties
// spring.resources.* => ResourceProperties
// spring.mvc.* => WebMvcProperties
@EnableConfigurationProperties({ ServerProperties.class, ResourceProperties.class,
		WebMvcProperties.class })
public class ErrorMvcAutoConfiguration {

	private final ServerProperties serverProperties;

	private final DispatcherServletPath dispatcherServletPath;

	private final List<ErrorViewResolver> errorViewResolvers;

	public ErrorMvcAutoConfiguration(ServerProperties serverProperties,
			DispatcherServletPath dispatcherServletPath,
			ObjectProvider<ErrorViewResolver> errorViewResolvers) {
		this.serverProperties = serverProperties;
		this.dispatcherServletPath = dispatcherServletPath;
		this.errorViewResolvers = errorViewResolvers.orderedStream()
				.collect(Collectors.toList());
	}

    // 在 bean ErrorAttributes 不存在的情况下定义bean ErrorAttributes,使用实现类 DefaultErrorAttributes
	@Bean
	@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
	public DefaultErrorAttributes errorAttributes() {
        // 是否包含异常信息取决于配置属性 server.error.includeException,
        // 如果不存在该配置属性,使用缺省值 false
		return new DefaultErrorAttributes(
				this.serverProperties.getError().isIncludeException());
	}

    // 在 bean ErrorController 不存在的情况下定义bean ErrorController, 使用实现类 BasicErrorController
    // 映射到URL路径 : ${server.error.path:${error.path:/error}}
	@Bean
	@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
	public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
		return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
				this.errorViewResolvers);
	}

    // 定义 bean ErrorPageCustomizer
	@Bean
	public ErrorPageCustomizer errorPageCustomizer() {
		return new ErrorPageCustomizer(this.serverProperties, this.dispatcherServletPath);
	}

    // 定义 bean PreserveErrorControllerTargetClassPostProcessor,
    // 该 BeanFactoryPostProcessor 确保 ErrorController bean 不会被创建相应的 AOP 代理对象
	@Bean
	public static PreserveErrorControllerTargetClassPostProcessor preserveErrorControllerTargetClassPostProcessor() {
		return new PreserveErrorControllerTargetClassPostProcessor();
	}

    // 内嵌配置类 : 定义缺省的 ViewResolver 
	@Configuration
	static class DefaultErrorViewResolverConfiguration {

		private final ApplicationContext applicationContext;

		private final ResourceProperties resourceProperties;

		DefaultErrorViewResolverConfiguration(ApplicationContext applicationContext,
				ResourceProperties resourceProperties) {
			this.applicationContext = applicationContext;
			this.resourceProperties = resourceProperties;
		}

		// 1. 在 bean DispatcherServlet 存在的前提下定义该bean
		// 2. 在DefaultErrorViewResolver类型的 bean 未被定义的前提下定义该bean,
		// 使用实现类 DefaultErrorViewResolver, 该 ViewResolver 会基于一些常见
		// 约定,尝试根据HTTP错误状态码解析出错误处理视图。它会在目录/error下针
		// 对提供的HTTP错误状态码搜索模板或者静态资源,比如,给定了HTTP状态码404,
		// 它会尝试搜索如下模板或者静态资源:
		// /<templates>/error/404.<ext> – 这里<templates>表示所配置的模板所在目录,<ext>表示所用的模板的文件名
		// /<static>/error/404.html – 这里<static>表示静态资源文件所在路径
		// /<templates>/error/4xx.<ext>
		// /<static>/error/4xx.html 
		@Bean
		@ConditionalOnBean(DispatcherServlet.class)
		@ConditionalOnMissingBean
		public DefaultErrorViewResolver conventionErrorViewResolver() {
			return new DefaultErrorViewResolver(this.applicationContext,
					this.resourceProperties);
		}

	}

    // 嵌套配置类
    // 配置著名的 Spring Whitelabel Error View, 
    // 在开发人员没有通过其他方式提供 Error View Template 的情况下却省总是配置
    // Whitelable Error View,
    // 通过配置项 server.error.whitelabel.enabled=false 可以禁用该配置,也就是
    // 不使用 Whitelabel Error View 机制
	@Configuration
	@ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", matchIfMissing = true)
	@Conditional(ErrorTemplateMissingCondition.class)
	protected static class WhitelabelErrorViewConfiguration {

		private final StaticView defaultErrorView = new StaticView();

        // 在名称为 error 的 bean View 不存在的情况下定义该 View bean,名字使用 error,
        // 该 View bean 的实现类是 StaticView,定义如下,该 View 实现对应产生一个
        // HTML 页面展示错误信息
		@Bean(name = "error")
		@ConditionalOnMissingBean(name = "error")
		public View defaultErrorView() {
			return this.defaultErrorView;
		}

		// If the user adds @EnableWebMvc then the bean name view resolver from
		// WebMvcAutoConfiguration disappears, so add it back in to avoid disappointment.
		// 如果 bean BeanNameViewResolver 不存在则定义该bean,
		// 该 bean是一个通过 bean 名称解析 View 的视图解析器,可以保障上面的
		// 名字为 error 的 defaultErrorView 被解析成功
		@Bean
		@ConditionalOnMissingBean
		public BeanNameViewResolver beanNameViewResolver() {
			BeanNameViewResolver resolver = new BeanNameViewResolver();
			resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
			return resolver;
		}

	}

	/**
	 *  SpringBootCondition that matches when no error template view is detected.
	 */
	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());
		}

	}

	/**
	 * Simple View implementation that writes a default HTML error page.
	 */
	private static class StaticView implements View {

		private static final Log logger = LogFactory.getLog(StaticView.class);

		@Override
		public void render(Map<String, ?> model, HttpServletRequest request,
				HttpServletResponse response) throws Exception {
			if (response.isCommitted()) {
				String message = getMessage(model);
				logger.error(message);
				return;
			}
			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>")
					.append("<div>There was an unexpected error (type=")
					.append(htmlEscape(model.get("error"))).append(", status=")
					.append(htmlEscape(model.get("status"))).append(").</div>");
			if (message != null) {
				builder.append("<div>").append(htmlEscape(message)).append("</div>");
			}
			if (trace != null) {
				builder.append("<div style='white-space:pre-wrap;'>")
						.append(htmlEscape(trace)).append("</div>");
			}
			builder.append("</body></html>");
			response.getWriter().append(builder.toString());
		}

		private String htmlEscape(Object input) {
			return (input != null) ? HtmlUtils.htmlEscape(input.toString()) : null;
		}

		private String getMessage(Map<String, ?> model) {
			Object path = model.get("path");
			String message = "Cannot render error page for request [" + path + "]";
			if (model.get("message") != null) {
				message += " and exception [" + model.get("message") + "]";
			}
			message += " as the response has already been committed.";
			message += " As a result, the response may have the wrong status code.";
			return message;
		}

		@Override
		public String getContentType() {
			return "text/html";
		}

	}

	/**
	 * WebServerFactoryCustomizer that configures the server's error pages.
	 */
	private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {

		private final ServerProperties properties;

		private final DispatcherServletPath dispatcherServletPath;

		protected ErrorPageCustomizer(ServerProperties properties,
				DispatcherServletPath dispatcherServletPath) {
			this.properties = properties;
			this.dispatcherServletPath = dispatcherServletPath;
		}

       // 根据错误有关配置属性构建一个 ErrorPage 对象,注册到 errorPageRegistry,
       // errorPageRegistry,这里 ErrorPageRegistry 其实就是 Spring MVC 提供的
       // 针对具体 Servlet Web 服务器的工厂类,比如对于 Tomcat Servlet 容器,
       // 它其实就是 TomcatServletWebServerFactory
		@Override
		public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
			ErrorPage errorPage = new ErrorPage(this.dispatcherServletPath
					.getRelativePath(this.properties.getError().getPath()));
			errorPageRegistry.addErrorPages(errorPage);
		}

		@Override
		public int getOrder() {
			return 0;
		}

	}

	/**
	 *  BeanFactoryPostProcessor to ensure that the target class of ErrorController
	 * MVC beans are preserved when using AOP.
	 *     
	 * 确保 ErrorController bean 被AOP创建代理对象的话,代理对象总是代理目标类而不是接口,
	 * 具体做法是对该 bean定义设置属性 :
	 * org.springframework.aop.framework.autoproxy.AutoProxyUtils.preserveTargetClass = true
	 */
	static class PreserveErrorControllerTargetClassPostProcessor
			implements BeanFactoryPostProcessor {

		@Override
		public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
				throws BeansException {
			String[] errorControllerBeans = beanFactory
					.getBeanNamesForType(ErrorController.class, false, false);
			for (String errorControllerBean : errorControllerBeans) {
				try {
					beanFactory.getBeanDefinition(errorControllerBean).setAttribute(
							AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
				}
				catch (Throwable ex) {
					// Ignore
				}
			}
		}

	}

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值