学透Spring Boot — 014. Spring MVC的自动配置

这是学透Spring Boot的第14篇文章,更多文章请移步我的专栏:

学透 Spring Boot_postnull咖啡的博客-CSDN博客

目录

[没有Spring Boot时的Spring MVC](#没有Spring Boot时的Spring MVC)

[使用Spring Boot后的Spring MVC](#使用Spring Boot后的Spring MVC)

[Spring MVC的自动配置解析](#Spring MVC的自动配置解析)

明确目标

入口类

Spring容器的启动

Spring容器生命周期hook类

[配置类解析类 ConfigurationClassParser](#配置类解析类 ConfigurationClassParser)

[自动配置类列表 AutoConfiguration.imports](#自动配置类列表 AutoConfiguration.imports)

[Spring MVC的自动配置类WebMvcAutoConfiguration](#Spring MVC的自动配置类WebMvcAutoConfiguration)

自动配置的视图解析器

Http消息转换器自动配置类

[数据源自动配置类 DataSourceAutoConfiguration](#数据源自动配置类 DataSourceAutoConfiguration)

[RestClient的自动配置 RestClientAutoConfiguration](#RestClient的自动配置 RestClientAutoConfiguration)

嵌入式的web容器配置

DispatcherServlet的自动配置

总结


没有Spring Boot时的Spring MVC

早些年还没有Spring Boot的时候,我们开发一个Spring MVC应用,需要做一大堆的配置,而且和其它的项目比较,这些配置大部分都是大同小异的,我们也可以称之为样板配置。

所以每次新建一个项目,我们通常是复制一个项目,然后复制这个项目的配置,做少量的修改,虽然没有什么太大的问题,但是如果一不小心改错,可能半天都找不到问题。

可以参考之前的一篇文章,里面介绍了没有Spring Boot时完整的手动配置。

学透Spring Boot --- [二] Spring 和 Spring Boot的比较-CSDN博客

我们可以大概看看传统Spring MVC项目的配置

web.xml 中配置DispatchServlet

在 servlet-context.xml 中配置 Spring MVC 相关组件

这两份配置,非常冗余,因为绝大部分项目都大同小异。

最后再实现控制器

使用Spring Boot后的Spring MVC

如果使用Spring Boot,事情变得非常简单。

我们只要在我们的应用启动类添加一个注解 @SpringBootApplication

然后,所有的事情,SpringBoot都会自动帮我们完成。

简直是单车 到 摩托车的飞跃。

Spring MVC的自动配置解析

下面我们一步步来研究,Spring Boot是如何做到自动配置MVC的。

明确目标

自动配置的结果,就是把手动显示的配置,变成自动的配置。

比如servlet-context.xml中配置的视图解析器

Spring Boot 它会通过@Bean声明的方式,帮我们创建一个视图解析器的Bean

我们今天的任务,就是要搞清楚,Spring Boot在哪里以及什么时候,帮我创建的这个Bean。

入口类

首先我们的入口类,使用了一个注解@SpringBootApplication。

java 复制代码
@SpringBootApplication
public class JoeLabApplication {

    public static void main(String[] args) {
        SpringApplication.run(JoeLabApplication.class, args);
    }
}

它其实是个组合注解。

java 复制代码
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
public @interface SpringBootApplication {

我们重点关注@EnableAutoConfiguration 这个注解,它是一个总开关,开启了自动配置的新世界。

这个注解也是一个组合注解。

java 复制代码
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

通过@Import(AutoConfigurationImportSelector.class)这个注解,会触发自动配置类的导入,spring boot会用这个类去完成自动配置的功能。

Spring容器的启动

这次,我们先看看Spring Boot的启动,来分析自动配置是如何生效。

java 复制代码
public class SpringApplication {
	public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
		return run(new Class<?>[] { primarySource }, args);
	}
}

SpringBoot的run方法,会创建并刷新Spring容器。

java 复制代码
public ConfigurableApplicationContext run(String... args) {
    context = createApplicationContext();
    context.setApplicationStartup(this.applicationStartup);
    prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
    refreshContext(context); // 刷新 Spring 容器,加载各种 Bean
    afterRefresh(context, applicationArguments);
    startup.started();
}

关注:refreshContext(context); // 刷新 Spring 容器,加载各种 Bean

接着刷新容器

刷新容器的关键过程包括 Bean 的加载与初始化。refreshContext 方法会启动各类 Bean 的生命周期,调用 invokeBeanFactoryPostProcessors 来执行 BeanFactory 后处理器。

java 复制代码
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
    PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, this.getBeanFactoryPostProcessors());
    if (!NativeDetector.inNativeImage() && beanFactory.getTempClassLoader() == null && beanFactory.containsBean("loadTimeWeaver")) {
        beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
        beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
    }
}

重点关注:invokeBeanFactoryPostProcessors

我们理解成Spring提供的生命周期钩子就行。通过这些钩子,我们可以在Spring启动过程中,做一些特殊的工作。比如自动化配置各种bean。

Spring容器生命周期hook类

其中Spring就提供了一个钩子类。

它是生命周期类,所以启动过程中自动被找到并执行。

java 复制代码
public class ConfigurationClassPostProcessor 
    implements BeanDefinitionRegistryPostProcessor, 
    BeanRegistrationAotProcessor, 
    BeanFactoryInitializationAotProcessor, 
    PriorityOrdered, 
    ResourceLoaderAware, 
    ApplicationStartupAware, 
    BeanClassLoaderAware, 
    EnvironmentAware {
}

这个类会去处理配置类 也就是加了@Configuration的类

java 复制代码
    public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
                StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
                parser.parse(candidates);
                parser.validate();
    }

配置类解析类 ConfigurationClassParser

接下来,它把任务交给了解析器------ConfigurationClassParser

最终这个解析工具类,通过一长串的调用链,最终到了另一个工具类ImportCandidates

java 复制代码
public final class ImportCandidates implements Iterable<String> {
	public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
		Assert.notNull(annotation, "'annotation' must not be null");
		ClassLoader classLoaderToUse = decideClassloader(classLoader);
		String location = String.format(LOCATION, annotation.getName());
		Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
		List<String> importCandidates = new ArrayList<>();
		while (urls.hasMoreElements()) {
			URL url = urls.nextElement();
			importCandidates.addAll(readCandidateConfigurations(url));
		}
		return new ImportCandidates(importCandidates);
	}
}

我们重点看这一行代码

java 复制代码
String location = String.format(LOCATION, annotation.getName());

其中:private static final String LOCATION = "META-INF/spring/%s.imports";

所以location是:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

这个文件,在我们的自动配置模块下

自动配置类列表 AutoConfiguration.imports

我们把这个文件的内容罗列出来

是不是很多名字都似曾相识呢?

是的,这就是SpringBoot提供的自动配置类,对各种常用的组件,都提供了自动配置类。

SpringBoot在启动Spring容器的过程中,会定位到这个文件,然后逐个尝试去加载配置类。

Spring MVC的自动配置类WebMvcAutoConfiguration

我们先重点关注其中一个WebMvcAutoConfiguration

这个类提就是Spring MVC的自动配置类。

这个配置类会被找到,但是要不要加载,得看条件。条件配置就是它上面的注解。

我们逐条解析:

@AutoConfiguration(after = { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,

ValidationAutoConfiguration.class }

DispatcherServlet配置完后,才会配置Spring MVC。

说得通,就像我们先配置web.xml中的DispatcherServlet,再配置Spring mvc的配置servlet.xml
@ConditionalOnWebApplication(type = Type.SERVLET)

必须是Spring MVC(Servlet)的web才会加载。

如果是WebFlux的web,就不会自动配置MVC。
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })

Classpath下有这个两个类。

表示我们引入了Spring mvc的依赖。
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)

只有当Spring容器没有WebMvcConfigurationSupport这个bean时,才会配置MVC。

因为这个Bean是用来给我们自定义的,如果我们不想用自动配置,而是想覆盖默认配置,我们就需要继承这个类。这样,SpringBoot就以我们配置的为主,而忽略自动配置。

这些条件,我们都满足,所以Spring Boot开始用这个类进行自动配置MVC。

这个配置类中定义了很多Bean,这些bean就是MVC的组件。

自动配置的视图解析器

其中,就包含默认的视图解析器。

java 复制代码
		@Bean
		@ConditionalOnMissingBean
		public InternalResourceViewResolver defaultViewResolver() {
			InternalResourceViewResolver resolver = new InternalResourceViewResolver();
			resolver.setPrefix(this.mvcProperties.getView().getPrefix());
			resolver.setSuffix(this.mvcProperties.getView().getSuffix());
			return resolver;
		}

还记得我们在传统Spring MVC项目手动的配置吗?是的,我们做到了,通过自动创建Bean的方式,成功完成了视图解析器的配置。

我们在看看其它的自动配置类。

都定义在org.springframework.boot.autoconfigure.AutoConfiguration.imports这个文件下。

Http消息转换器自动配置类

java 复制代码
@AutoConfiguration(
		after = { GsonAutoConfiguration.class, JacksonAutoConfiguration.class, JsonbAutoConfiguration.class })
@ConditionalOnClass(HttpMessageConverter.class)
@Conditional(NotReactiveWebApplicationCondition.class)
@Import({ JacksonHttpMessageConvertersConfiguration.class, GsonHttpMessageConvertersConfiguration.class,
		JsonbHttpMessageConvertersConfiguration.class })
@ImportRuntimeHints(HttpMessageConvertersAutoConfigurationRuntimeHints.class)
public class HttpMessageConvertersAutoConfiguration {

它导入了三种解析器

java 复制代码
@Import({ 
    JacksonHttpMessageConvertersConfiguration.class,         
    GsonHttpMessageConvertersConfiguration.class,
	JsonbHttpMessageConvertersConfiguration.class })

其中Jackson的是

java 复制代码
		@Bean
		@ConditionalOnMissingBean(value = MappingJackson2HttpMessageConverter.class,
				ignoredType = {
						"org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter",
						"org.springframework.data.rest.webmvc.alps.AlpsJsonHttpMessageConverter" })
		MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
			return new MappingJackson2HttpMessageConverter(objectMapper);
		}

数据源自动配置类 DataSourceAutoConfiguration

java 复制代码
	@Configuration(proxyBeanMethods = false)
	@Conditional(PooledDataSourceCondition.class)
	@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
	@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
			DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class,
			DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })
	protected static class PooledDataSourceConfiguration {

		@Bean
		@ConditionalOnMissingBean(JdbcConnectionDetails.class)
		PropertiesJdbcConnectionDetails jdbcConnectionDetails(DataSourceProperties properties) {
			return new PropertiesJdbcConnectionDetails(properties);
		}

	}

RestClient的自动配置 RestClientAutoConfiguration

java 复制代码
	@Bean
	@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
	@ConditionalOnMissingBean
	RestClient.Builder restClientBuilder(RestClientBuilderConfigurer restClientBuilderConfigurer) {
		return restClientBuilderConfigurer.configure(RestClient.builder());
	}

嵌入式的web容器配置

java 复制代码
@AutoConfiguration
@ConditionalOnNotWarDeployment
@ConditionalOnWebApplication
@EnableConfigurationProperties(ServerProperties.class)
public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {

	/**
	 * Nested configuration if Tomcat is being used.
	 */
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class })
	public static class TomcatWebServerFactoryCustomizerConfiguration {

		@Bean
		public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment,
				ServerProperties serverProperties) {
			return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
		}

		@Bean
		@ConditionalOnThreading(Threading.VIRTUAL)
		TomcatVirtualThreadsWebServerFactoryCustomizer tomcatVirtualThreadsProtocolHandlerCustomizer() {
			return new TomcatVirtualThreadsWebServerFactoryCustomizer();
		}

	}

DispatcherServlet的自动配置

java 复制代码
		@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
		public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
			DispatcherServlet dispatcherServlet = new DispatcherServlet();
			dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
			dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
			dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
			dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
			return dispatcherServlet;
		}

总结

我们这篇文章,从另一个角度------Spring容器的启动过程,结合SpringBoot提供的注解,理解了Spring Boot的自动配置原理。

最终定义到自动配置类的列表文件:

org.springframework.boot.autoconfigure.AutoConfiguration.imports

相关推荐
武昌库里写JAVA2 小时前
SpringCloud
vue.js·spring boot·毕业设计·layui·课程设计
直视太阳3 小时前
springboot+easyexcel实现下载excels模板下拉选择
java·spring boot·后端
zkmall4 小时前
HikariCP 源码核心设计解析与 ZKmall开源商城场景调优实践
spring boot·开源
程序媛学姐4 小时前
SpringRabbitMQ消息模型:交换机类型与绑定关系
java·开发语言·spring
兰亭序咖啡4 小时前
学透Spring Boot — 009. Spring Boot的四种 Http 客户端
java·spring boot·后端
zew10409945884 小时前
基于spring boot的外卖系统的设计与实现【如何写论文思路与真正写出论文】
spring boot·后端·毕业设计·论文·外卖系统·辅导·查重
兰亭序咖啡5 小时前
学透Spring Boot — 018. 优雅支持多种响应格式
java·spring boot·后端
信徒_6 小时前
Spring 怎么解决循环依赖问题?
java·后端·spring
小杨4047 小时前
springboot框架项目实践应用十五(扩展sentinel区分来源)
spring boot·后端·spring cloud