SpringBoot — WebMvc自动装配

小龙 1,572 2022-07-06

SpringWebMvc 自动装配大纲:

image-1657072391235

在引入 spring-boot-starter-web 的依赖后,SpringBoot 会自动进行 Web 环境的装载,按照 SpringFramework 一贯的规律,SpringWebMvc 的自动装配类应该叫:WebMvcAutoConfiguration

WebMvcAutoConfiguration

@Configuration
//当前环境必须是WebMvc(Servlet)环境
@ConditionalOnWebApplication(type = Type.SERVLET)
//当前运行环境的classpath中必须有Servlet类,DispatcherServlet类,WebMvcConfigurer类
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
//如果没有自定义WebMvc的配置类,则使用本自动配置
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration

在配置中标注了,WebMvcAutoConfiguration 必须在 DispatcherServletAutoConfigurationTaskExecutionAutoConfigurationValidationAutoConfiguration 执行完后再执行。

DispatcherServletAutoConfiguration

/**
* DispatcherServlet 的自动配置。它起作用应该依赖于一个已经存在嵌入式Web服务器的独立应用程序,也适用于使用 SpringBootServletInitializer 的可部署应用程序。
*/
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration

其中 SpringBootServletInitializer 是SpringBoot用于打war包时留给Web容器初始化应用的钩子。

DispatcherServletAutoConfiguration 的源码中又标注了 @AutoConfigureAfter ,说明它又要在 ServletWebServerFactoryAutoConfiguration 之后再执行。

ServletWebServerFactoryAutoConfiguration

@Configuration
//在自动配置中具有最高优先级执行
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
        ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
        ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
        ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration

这个类又导入了几个组件:EmbeddedTomcatEmbeddedJettyEmbeddedUndertowBeanPostProcessorsRegistrar

EmbeddedTomcat

@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
    @Bean
    public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
        return new TomcatServletWebServerFactory();
    }
}

由条件装配的注解 @ConditionalOnClass 可以看到,当前 classpath 下必须有 Tomcat 这个类,该配置类才会生效。对比 Jetty:

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.webapp.WebAppContext;

@Configuration
@ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedJetty

默认导入的 spring-boot-starter-web 中导入的是 Tomcat 的依赖,故 Jetty 不会生效。

ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar

public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {

    // ......

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
            BeanDefinitionRegistry registry) {
        if (this.beanFactory == null) {
            return;
        }
        // 编程式注入组件
        registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor",
                WebServerFactoryCustomizerBeanPostProcessor.class);
        registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor",
                ErrorPageRegistrarBeanPostProcessor.class);
    }

    private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry, String name, Class<?> beanClass) {
        if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(beanClass, true, false))) {
            RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
            beanDefinition.setSynthetic(true);
            registry.registerBeanDefinition(name, beanDefinition);
        }
    }
}

它实现了 ImportBeanDefinitionRegistrar 接口,在registerBeanDefinitions 中可以编程式向IOC容器中注入组件。它注册的两个组件是:WebServerFactoryCustomizerBeanPostProcessorErrorPageRegistrarBeanPostProcessor

WebServerFactoryCustomizerBeanPostProcessor

Bean的后置处理器,它将 Bean 工厂中的所有 WebServerFactoryCustomizer 类型的 Bean 应用于 WebServerFactory 类型的 Bean。

它的作用是执行组件定制器

ErrorPageRegistrarBeanPostProcessor

Bean的后置处理器,它将Bean工厂中的所有 ErrorPageRegistrars 应用于 ErrorPageRegistry 类型的Bean。

它的作用是将所有设置的错误页跳转规则注册到错误处理器中。

ErrorPageRegistry 接口的源码:

public interface ErrorPageRegistry {
   /**
    * Adds error pages that will be used when handling exceptions.
    * 添加错误页面
    */
   void addErrorPages(ErrorPage... errorPages);
}

SpringBoot中的Customizer(定制器)

一般情况下,修改 SpringBoot 的配置,都是通过 application.yml 显示的声明配置。除此之外,还可以使用 Customizer 定制器机制

在 WebMvc 模块中,使用 Customizer 修改配置,可以实现 WebServletFactoryCustomizer 接口。该接口可以传入泛型,泛型的类型是 ServletWebServerFactory

例:

@Order(0)
@Component
public class WebMvcCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory>, Ordered {
    @Override
    public void customize(TomcatServletWebServerFactory factory) {
        factory.setPort(9090);
        factory.setContextPath("/demo");
    }
    
    @Override
    public int getOrder() {
        return 0;
    }  
}

Customizer 可以设置配置顺序(上面的 @Order 注解,或 Ordered 接口),通过配置执行顺序,可以自定义的覆盖某些自动配置,达到个性化配置的目的。

提这个 Customizer 机制,是为了看下面的配置类:

ServletWebServerFactoryAutoConfiguration中注册的其他组件

public class ServletWebServerFactoryAutoConfiguration {

	@Bean
	public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties) {
		return new ServletWebServerFactoryCustomizer(serverProperties);
	}

	@Bean
	@ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
	public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
			ServerProperties serverProperties) {
		return new TomcatServletWebServerFactoryCustomizer(serverProperties);
	}
}

这里创建了两个定制器,并把 ServerProperties 传入,让定制器根据配置信息做自动化配置。

ServerProperties的来源

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {

	/**
	 * Server HTTP port.
	 */
	private Integer port;

	/**
	 * Network address to which the server should bind.
	 */
	private InetAddress address;
    
    // ......

@ConfigurationProperties 的作用:可用于某个类上,设置属性profix用于指定在工程的全局配置文件(application.properties 或 application.yml)中的配置的根信息。

简言之, @ConfigurationProperties 可以实现指定属性开头的属性值注入。

那么 ServerProperties 的属性值来源,就是全局配置文件中的server开头的所有配置。

以上执行完毕后,ServletWebServerFactoryAutoConfiguration 的全部配置也就完成了,下面执行 DispatcherServletAutoConfiguration 。

DispatcherServletAutoConfiguration

// 最高配置优先级
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
// Servlet环境下才生效
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {

    public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";

    public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";

    // 注册DispatcherServlet的配置类
    @Configuration
    @Conditional(DefaultDispatcherServletCondition.class)
    @ConditionalOnClass(ServletRegistration.class)
    // 启用配置文件与Properties的映射
    @EnableConfigurationProperties({ HttpProperties.class, WebMvcProperties.class })
    protected static class DispatcherServletConfiguration {

        private final HttpProperties httpProperties;

        private final WebMvcProperties webMvcProperties;

        public DispatcherServletConfiguration(HttpProperties httpProperties, WebMvcProperties webMvcProperties) {
            this.httpProperties = httpProperties;
            this.webMvcProperties = webMvcProperties;
        }

        // 构造DispatcherServlet
        @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
        public DispatcherServlet dispatcherServlet() {
            DispatcherServlet dispatcherServlet = new DispatcherServlet();
            dispatcherServlet.setDispatchOptionsRequest(this.webMvcProperties.isDispatchOptionsRequest());
            dispatcherServlet.setDispatchTraceRequest(this.webMvcProperties.isDispatchTraceRequest());
            dispatcherServlet
                .setThrowExceptionIfNoHandlerFound(this.webMvcProperties.isThrowExceptionIfNoHandlerFound());
            dispatcherServlet.setEnableLoggingRequestDetails(this.httpProperties.isLogRequestDetails());
            return dispatcherServlet;
        }

        // 注册文件上传组件
        @Bean
        @ConditionalOnBean(MultipartResolver.class)
        @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
        public MultipartResolver multipartResolver(MultipartResolver resolver) {
            // Detect if the user has created a MultipartResolver but named it incorrectly
            return resolver;
        }
    }

    // 注册DispatcherServletRegistration的配置类
    @Configuration
    @Conditional(DispatcherServletRegistrationCondition.class)
    @ConditionalOnClass(ServletRegistration.class)
    @EnableConfigurationProperties(WebMvcProperties.class)
    @Import(DispatcherServletConfiguration.class)
    protected static class DispatcherServletRegistrationConfiguration {

        private final WebMvcProperties webMvcProperties;

        private final MultipartConfigElement multipartConfig;

        public DispatcherServletRegistrationConfiguration(WebMvcProperties webMvcProperties,
                ObjectProvider<MultipartConfigElement> multipartConfigProvider) {
            this.webMvcProperties = webMvcProperties;
            this.multipartConfig = multipartConfigProvider.getIfAvailable();
        }

        // 辅助注册DispatcherServlet的RegistrationBean
        @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
        @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
        public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet) {
            DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
                    this.webMvcProperties.getServlet().getPath());
            registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
            registration.setLoadOnStartup(this.webMvcProperties.getServlet().getLoadOnStartup());
            if (this.multipartConfig != null) {
                registration.setMultipartConfig(this.multipartConfig);
            }
            return registration;
        }
    }
//......

里面嵌套了两个内部类,分别注册 DispatcherServletDispatcherServletRegistrationBean

SpringBoot注册Servlet等组件

由于 SpringBoot 项目中没有 web.xml(Servlet3.0规范中就没有了),故有另外的方式注册Servlet三大组件。SpringBoot 提供两种方式。

  1. 组件扫描@ServletComponentScan

在启动类上标注 @ServletComponentScan 注解,指定 value/basePackage,即可扫描指定包及子包下所有的 Servlet 组件。

之后注册 Servlet、Filter、Listener 组件,就可以像 Servlet3.0 规范后的方式,直接在 Servlet 上标注 @WebServlet 等注解即可。

  1. 借助RegistrationBean

自定义的Servlet可以创建 ServletRegistrationBean<T extends Servlet>

使用时,只需要在配置类中注册一个 ServletRegistrationBean,创建它的对象时,使用有参构造方法,传入 Servlet 和 urlMapping 即可。

例:

public class DemoServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().println("demo servlet");
    }
}

public class DemoServletRegistryBean extends ServletRegistrationBean<DemoServlet> {
    public DemoServletRegistryBean(DemoServlet servlet, String... urlMappings) {
        super(servlet, urlMappings);
    }
}

@Configuration
public class ServletConfiguration {
    @Bean
    public DemoServletRegistryBean demoServletRegistryBean() {
        return new DemoServletRegistryBean(new DemoServlet(), "/demo/servlet");
    }
}

注册DispatcherServlet

@EnableConfigurationProperties({ HttpProperties.class, WebMvcProperties.class })
protected static class DispatcherServletConfiguration {

    private final HttpProperties httpProperties;

    private final WebMvcProperties webMvcProperties;

    public DispatcherServletConfiguration(HttpProperties httpProperties, WebMvcProperties webMvcProperties) {
        this.httpProperties = httpProperties;
        this.webMvcProperties = webMvcProperties;
    }

    @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public DispatcherServlet dispatcherServlet() {
        DispatcherServlet dispatcherServlet = new DispatcherServlet();
        dispatcherServlet.setDispatchOptionsRequest(this.webMvcProperties.isDispatchOptionsRequest());
        dispatcherServlet.setDispatchTraceRequest(this.webMvcProperties.isDispatchTraceRequest());
        dispatcherServlet
                .setThrowExceptionIfNoHandlerFound(this.webMvcProperties.isThrowExceptionIfNoHandlerFound());
        dispatcherServlet.setEnableLoggingRequestDetails(this.httpProperties.isLogRequestDetails());
        return dispatcherServlet;
    }

DispatcherServletConfiguration 类上标注了 @EnableConfigurationProperties,代表启用指定类的 ConfigurationProperties 功能。

下面创建 DispatcherServlet,并将默认的一些配置设置到 DispatcherServlet 中。这些属性就来自于已经被启用的 HttpProperties、WebMvcProperties 中。

WebMvcConfiguration

在SpringBoot2.x中,自定义的WebMvc配置需要实现 WebMvcConfigurer 接口,并重写接口中需要配置的方法即可。

WebMvcAutoConfigurationAdapter 也实现了该接口,并进行默认配置。

@Configuration
// 导入配置类
@Import(EnableWebMvcConfiguration.class)
// 启用WebMvcProperties、ResourceProperties
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ResourceLoaderAware

配置HttpMessageConverter

@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    this.messageConvertersProvider
            .ifAvailable((customConverters) -> converters.addAll(customConverters.getConverters()));
}

ViewResolver的组件注册

// 最常用的视图解析器
@Bean
@ConditionalOnMissingBean
public InternalResourceViewResolver defaultViewResolver() {
    InternalResourceViewResolver resolver = new InternalResourceViewResolver();
    // 使用前后缀拼接的方式
    resolver.setPrefix(this.mvcProperties.getView().getPrefix());
    resolver.setSuffix(this.mvcProperties.getView().getSuffix());
    return resolver;
}

@Bean
@ConditionalOnBean(View.class)
@ConditionalOnMissingBean
public BeanNameViewResolver beanNameViewResolver() {
    BeanNameViewResolver resolver = new BeanNameViewResolver();
    resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
    return resolver;
}

@Bean
@ConditionalOnBean(ViewResolver.class)
@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
    ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
    resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
    // ContentNegotiatingViewResolver uses all the other view resolvers to locate
    // a view so it should have a high precedence
    resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
    return resolver;
}

// 国际化组件
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
public LocaleResolver localeResolver() {
    if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
        return new FixedLocaleResolver(this.mvcProperties.getLocale());
    }
    AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
    localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
    return localeResolver;
}

注册了 ViewResolverLocaleResolver

  • ContentNegotiatingViewResolver:最高级的 ViewResolver,负责将视图解析的工作代理给不同的 ViewResolver 来处理不同的View
  • BeanNameViewResolver:如果 Controller 中返回的视图名称恰好有一个Bean的名称与之相同,则会交予Bean处理
  • InternalResourceViewResolver:最常用的 ViewResolver,通过设置前后缀来匹配视图

5.1.3 静态资源映射

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    if (!this.resourceProperties.isAddMappings()) {
        logger.debug("Default resource handling disabled");
        return;
    }
    Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
    CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
    // 映射webjars
    if (!registry.hasMappingForPattern("/webjars/**")) {
        customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/")
                .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
    }
    // 映射静态资源路径
    String staticPathPattern = this.mvcProperties.getStaticPathPattern();
    if (!registry.hasMappingForPattern(staticPathPattern)) {
        customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
                .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
                .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
    }
}

注册静态资源路径。可以看到,它将** /webjars** 路径下的资源都映射到 classpath:/META-INF/resources/webjars 中。

除了注册 webjars 的资源路径,倒数第二行,还取到 resourceProperties 中的 staticLocations,也加入进去。

而 ResourceProperties 中的 staticLocations:

private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
        "classpath:/resources/", "classpath:/static/", "classpath:/public/" };

private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;

这也解释了为什么静态资源文件放在 resources 中和放在 static 中都能被正常加载的原因。

主页的设置

@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext) {
    // 调用getWelcomePage,跳转到下面的方法中
    return new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext),
            applicationContext, getWelcomePage(), this.mvcProperties.getStaticPathPattern());
}

static String[] getResourceLocations(String[] staticLocations) {
    String[] locations = new String[staticLocations.length + SERVLET_LOCATIONS.length];
    System.arraycopy(staticLocations, 0, locations, 0, staticLocations.length);
    System.arraycopy(SERVLET_LOCATIONS, 0, locations, staticLocations.length, SERVLET_LOCATIONS.length);
    return locations;
}

private Optional<Resource> getWelcomePage() {
    String[] locations = getResourceLocations(this.resourceProperties.getStaticLocations());
    // this::getIndexHtml调用下面的方法
    return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();
}

private Resource getIndexHtml(String location) {
    return this.resourceLoader.getResource(location + "index.html");
}

由此可以看出,欢迎页面/主页的设置,是取的静态资源路径中的** index.html** 文件。

应用图标的设置

@Configuration
@ConditionalOnProperty(value = "spring.mvc.favicon.enabled", matchIfMissing = true)
public static class FaviconConfiguration implements ResourceLoaderAware {
    // ......
    // 配置图标映射器
    @Bean
    public SimpleUrlHandlerMapping faviconHandlerMapping() {
        SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
        mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
        mapping.setUrlMap(Collections.singletonMap("**/favicon.ico", faviconRequestHandler()));
        return mapping;
    }
    // .......
}

可以明显的看到默认的图标名称是 favicon.ico,且放在静态路径下的任意位置都可以被扫描到。

EnableWebMvcConfiguration

注册的核心组件

SpringWebMvc 中最核心的两个组件:处理器适配器、处理器映射器:

// 处理器适配器
@Bean
@Override
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
    RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter();
    adapter.setIgnoreDefaultModelOnRedirect(
            this.mvcProperties == null || this.mvcProperties.isIgnoreDefaultModelOnRedirect());
    return adapter;
}

// 处理器映射器
@Bean
@Primary
@Override
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
    // Must be @Primary for MvcUriComponentsBuilder to work
    return super.requestMappingHandlerMapping();
}

注册了 Hibernate-Validator 参数校验器:

// 校验器
@Bean
@Override
public Validator mvcValidator() {
    if (!ClassUtils.isPresent("javax.validation.Validator", getClass().getClassLoader())) {
        return super.mvcValidator();
    }
    return ValidatorAdapter.get(getApplicationContext(), getValidator());
}

注册了全局异常处理器:

@Override
// 全局异常处理器
protected ExceptionHandlerExceptionResolver createExceptionHandlerExceptionResolver() {
    if (this.mvcRegistrations != null && this.mvcRegistrations.getExceptionHandlerExceptionResolver() != null) {
        return this.mvcRegistrations.getExceptionHandlerExceptionResolver();
    }
    return super.createExceptionHandlerExceptionResolver();
}

@Override
protected void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
    super.configureHandlerExceptionResolvers(exceptionResolvers);
    if (exceptionResolvers.isEmpty()) {
        addDefaultHandlerExceptionResolvers(exceptionResolvers);
    }
    if (this.mvcProperties.isLogResolvedException()) {
        for (HandlerExceptionResolver resolver : exceptionResolvers) {
            if (resolver instanceof AbstractHandlerExceptionResolver) {
                ((AbstractHandlerExceptionResolver) resolver).setWarnLogCategory(resolver.getClass().getName());
            }
        }
    }
}

总结

  1. 自动配置类是有执行顺序的, WebMvcAutoConfiguration 的执行顺序在 ServletWebServerFactoryAutoConfigurationDispatcherServletAutoConfiguration 之后。

  2. SpringBoot会根据当前classpath下的类来决定装配哪些组件,启动哪种类型的Web容器。

  3. WebMvc的配置包括消息转换器、视图解析器、处理器映射器、处理器适配器、静态资源映射配置、主页设置、应用图标设置等。

  4. 配置 SpringBoot 应用除了可以使用 properties、yml 之外,还可以使用 Customizer 来编程式配置。