【SpringBoot】之自动配置原理分析(源码级别)

目录


一、SpringBoot 简介


1、什么是 SpringBoot

SpringBoot 是 Spring 项目中的一个子工程,Spring Boot 与 Spring Framework、Spring Cloud 并称为 Spring 的三大核心产品。

SpringBoot 的官方地址:https://spring.io/projects/spring-boot

从官方文档我们可以看出,Spring Boot 是为了让我们能够简单快速地搭建独立的、能够直接运行的基于 Spring 的产品级应用程序。

SpringBoot 通过 Spring 平台和大量的第三方库,只需要最小化的 Spring 配置就能让我们快速的构建庞大的 Spring 项目,做到开箱即用,迅速上手,让我们关注与业务而非配置。

2、SpringBoot 的特点

SpringBoot 主要特点是:

  • 可以快速地创建独立的 Spring 应用程序;
  • 直接内嵌 Tomcat、Jetty 或 Undertow 服务器,无需手动部署 war 文件;
  • 通过完善的 starter 依赖体系,大大简化了应用程序的构建配置;
  • 尽可能地自动配置 Spring 和第三方库;
  • 提供了一些大型项目中常见的非功能性特性,如指标、健康检查和外部化配置等;
  • 绝对没有代码生成,也无需 XML 配置。

3、为什么使用 SpringBoot

在 SpringBoot 开始流行之前,我们一般都是使用 Strust2 或者 SpringMVC 框架来开发 web 应用,但是这两个框架都有一个特点就是配置非常的繁琐,要写一大堆的配置文件,虽然 Spring 在支持了注解开发之后稍微有些改善,但有的时候还是会比较麻烦。

这种情况下,SpringBoot 的优势就体现出来了,通过 SpringBoot 特有的自动配置就可以做到只需一个 propertiesyml 配置文件就可以简化 SpringMVC 中需要在 xml 中配置的一大堆的 bean。


二、SpringBoot 自动配置原理


1、分析 SpringBoot 的自动配置原理

每个 SpringBoot 工程的主启动类都有一个 @SpringBootApplication 注解,比如:

@SpringBootApplication(scanBasePackages = {"com.example.demo"})
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

我们点进入看一下这个注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

可以看到 SpringBootApplication 注解是由一系列的注解组合而成,其中最核心的就是 @EnableAutoConfiguration 注解,这个注解的含义就是开启自动装配,能够直接被各种组件的 bean 装配到 IOC 容器中,我们点进去看一下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

同样,EnableAutoConfiguration 也是一个复合注解,其中关键的注解是 @Import(AutoConfigurationImportSelector.class),这个 Import 注解导入了 AutoConfigurationImportSelector.class 这个类,而这个类是我们自动装配的导入选择器,点进这个类去,可以看到一个 selectImports() 核心方法:

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
	if (!isEnabled(annotationMetadata)) {
		return NO_IMPORTS;
	}
	// 加载元数据
	AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
			.loadMetadata(this.beanClassLoader);
	// 获取自动配置的 Entry 实体
	AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
			annotationMetadata);
	return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

我们点进 getAutoConfigurationEntry 这个方法去看一下:

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
			AnnotationMetadata annotationMetadata) {
	// 省略代码
	......
	// 核心代码:获取候选的配置类
	List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
	// 后面是对配置的去重、排除等操作
	......
}

这里面的核心方法就是 getCandidateConfigurations(),这个方法的作用是加载所有自动配置类的名字,源码如下:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
	// 通过工厂加载器加载自动配置类
	List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
			getBeanClassLoader());
	Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
			+ "are using a custom packaging, make sure that file is correct.");
	return configurations;
}

我们跟踪 loadFactoryNames() 方法来到了最终加载资源的方法 loadSpringFactories(),里面加载资源的核心代码是:

try {
	// 获取所有资源文件地址
	Enumeration<URL> urls = (classLoader != null ?
			classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
			ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
	result = new LinkedMultiValueMap<>();
	// 解析资源文件内容
	while (urls.hasMoreElements()) {
		URL url = urls.nextElement();
		UrlResource resource = new UrlResource(url);
		// 将资源文件中的内容封装成 Properties 对象
		Properties properties = PropertiesLoaderUtils.loadProperties(resource);
		for (Map.Entry<?, ?> entry : properties.entrySet()) {
			String factoryTypeName = ((String) entry.getKey()).trim();
			// 解析里面的 key-value 值
			for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
				result.add(factoryTypeName, factoryImplementationName.trim());
			}
		}
	}
	cache.put(classLoader, result);
	return result;
}

其中获取资源地址中的 FACTORIES_RESOURCE_LOCATION 常量的定义值为 META-INF/spring.factories

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

这块代码的核心流程是这样子的:

  • 首先,通过 classLoader.getResources(“META-INF/spring.factories”) 扫描所有 jar 包类路径下的 META-INF/spring.factories 文件,得到该文件的 URL 路径地址;
  • 接着,通过 PropertiesLoaderUtils.loadProperties() 方法将这些文件上的内容封装成 properties 对象;
  • 最后对 properties 对象的值进行解析成一个个 key-value 值,并打包返回最终结果,而这些返回结果就是我们要交给容器中的所有组件。

我们进入 SpringBoot 自动配置的 jar 包去看一下 META-INF/spring.factories 文件内容,具体所在路径如下(以 2.2.10版本为例):

在文件内容中找到 EnableAutoConfiguration 的部分值如下:

所以,在加载自动配置组件的代码中,factoryTypeName 就是 org.springframework.boot.autoconfigure.EnableAutoConfiguration,而 factoryImplementationName 就是对应的每个值,比如:org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,代表要自动配置的类。相当于把 EnableAutoConfiguration 值里面的这些组件都加到了容器中。

原理分析总结:

至此,我们基本上已经知道了整个自动装配的流程:扫描并获取所有 jar 包类路径下的 META-INF/spring.factories 文件的 URL 地址,将这些文件加载成 properties 对象,然后获取其中 EnableAutoConfiguration 所有的值,每个值都是自动配置类,最终都会添注入到容器中。


2、分析 Starter 组件的自动配置原理

上面我们分析了 SpringBoot 自动配置的原理,那么组件是如何进行自动装配的呢?SpringBoot 的 META-INF/spring.factories 文件中包含了上百个自动配置类,难道 SpringBoot 启动的时候就会把这些所有组件实例化到容器中?

下面,我们就来分析一下:自动配置的组件是如何自动装配到容器中的

我们以 Redis 为例,从 META-INF/spring.factories 文件中的 EnableAutoConfiguration 值中可以找到 Redis 的自动配置类为:org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,我们点进去看一下这个自动配置类的源码:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {

	@Bean
	@ConditionalOnMissingBean(name = "redisTemplate")
	public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
			throws UnknownHostException {
		RedisTemplate<Object, Object> template = new RedisTemplate<>();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

	@Bean
	@ConditionalOnMissingBean
	public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
			throws UnknownHostException {
		StringRedisTemplate template = new StringRedisTemplate();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

}

这里面有4个注解修饰着 RedisAutoConfiguration 自动配置类,我们逐一分析它们的作用。

先分析 @Configuration 这个注解,它表示所修饰的类为配置类,熟悉 Spring 开发的都知道,在配置类中,我们可以通过 @Bean 注解很方便的将组件的 Bean 注册到 IOC 容器中。这里,Redis 为我们注册了 redisTemplatestringRedisTemplate 两个 Bean,用于操作 Redis 服务器。同时使用 @ConditionalOnMissingBean 注解表示只有当 Bean 不存在时才注册到容器中。

接着再来分析一下 @EnableConfigurationProperties(RedisProperties.class) 这个注解,它的作用是用来生效 @ConfigurationProperties 注解的,显然,RedisProperties.class 就是个被 @ConfigurationProperties 注解所修饰的配置属性类:

@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
	......

	/**
	 * Redis server host.
	 */
	private String host = "localhost";

	/**
	 * Login password of the redis server.
	 */
	private String password;

	/**
	 * Redis server port.
	 */
	private int port = 6379;
	
	......
}

配置属性类是用来从 propertiesyml 配置文件中加载配置值的,通过 prefix 还可以指定配置的前缀名称,所以,我们可以这样配置 Redis 所需的属性配置:

spring:
	redis:
		host: http://localhost
		port: 6379
		password: 1234

这样,RedisProperties 在实例化的时候就会读取对应的值注入到相应的属性上。

再来看一下 @Import 这个注解,它给我们的自动配置类导入了两个类:LettuceConnectionConfiguration.classJedisConnectionConfiguration.class ,它们是用来连接 Redis 服务器的配置类,一个是使用 Lettuce,另一个使用 Jedis,这两个都是常用的连接 Redis 的组件。以 JedisConnectionConfiguration.class 为例:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ GenericObjectPool.class, JedisConnection.class, Jedis.class })
class JedisConnectionConfiguration extends RedisConnectionConfiguration {

	// 连接配置依赖 RedisProperties 属性配置
	JedisConnectionConfiguration(RedisProperties properties,
			ObjectProvider<RedisSentinelConfiguration> sentinelConfiguration,
			ObjectProvider<RedisClusterConfiguration> clusterConfiguration) {
		super(properties, sentinelConfiguration, clusterConfiguration);
	}

可以看到 Redis 连接配置类需要依赖 RedisProperties 属性配置类,很明显,只有通过属性配置类我们才能获取 Redis 服务器的地址、端口、密码等信息,有了这些信息才能连接到 Redis 服务器。

最后就是最关键的注解:@ConditionalOnClass(RedisOperations.class) ,这个注解决定了 RedisAutoConfiguration 是否会生效,它的意思是只有当存在 RedisOperations.class 这个类的时候才会生效 RedisAutoConfiguration,而 RedisOperations.class 是一个最终执行操作 Redis 命令的类,它的定义在 spring-data-redis 包下,所以,需要引入对应的依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

现在,我们可以对 Redis 组件自动配置原理进行总结了:

  • SpringBoot 在启动的时候会将 RedisAutoConfiguration 注入容器当中,但由于 @ConditionalOnClass 注解的限制,我们需要提供 RedisOperations.class 这个类的定义,而这个类在 spring-data-redis 包下,所以,只有引入了相应的依赖才能生效整个组件;
  • RedisAutoConfiguration 为我们提供了 redisTemplatestringRedisTemplate 这两个 Bean 用来操作 Redis;
  • 连接 Redis 服务器使用的是 Lettuce 或 Jedis 这两个组件,其中连接 Redis 服务器的配置信息来自 RedisProperties 这个配置属性类,里面包含了服务器的地址、端口、密码等信息,这些信息可以通过项目的 properties 或 yml 配置文件进行设置。

至此,我们已经基本知道了组件是如何被自动装配的了,关键点就是在 META-INF/spring.fatories 文件包含相应的自动配置类,然后再自动配置类中实例化相应的组件、属性配置类等各种 bean。


知识扩展:自动加载的相关注解


1)条件依赖注解:

  • @ConditionalOnClass:应用中包含某个类时,对应的配置才生效;
  • @ConditionalOnMissingClass:应用中不包含某个类时,对应的配置才生效;
  • @ConditionalOnBean:Spring 容器中存在指定 class 的实例对象时,对应的配置才生效;
  • @ConditionalOnMissionBean:Spring 容器中不存在指定 class 的实例对象时,对应的配置才生效;
  • @ConditionalOnProperty:项目配置文件中存在指定属性值时,对应的配置才生效;
  • @ConditionalOnResource:指定资源文件存在时,对应的配置才生效;
  • @ConditionalOnWebApplication:当前处于 Web 环境(WebApplicationContext)时,对应的配置才生效;
  • @ConditionalOnNotWebApplication:当前不处于 Web 环境时,对应的配置才生效;
  • @ConditionalOnExpression:项目配置文件中存在指定属性值时,对应的配置才生效;与 ConditionalOnProperty 不同的是,该注解使用的是 SpringEL 表达式;

2)加载顺序注解:

  • @AutoConfigureAfter:在指定的 Configuration 类之后加载;
  • @AutoConfigureBefore:在指定的 Configuration 类之前加载;
  • @AutoConfigureOrder:指定 Configuration 类的加载顺序,默认值为0;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值