前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringBoot自动装配原理解析

SpringBoot自动装配原理解析

作者头像
后场技术
发布2020-09-03 17:37:10
5640
发布2020-09-03 17:37:10
举报
文章被收录于专栏:后场技术后场技术

自动装配是Spring Boot的核心部分,也是Spring Boot功能的基础,正是由于自动装配,才将我们从Bean的繁复配置中解脱出来。那么Spring Boot中的自动装配指的是什么?我们继 续以Spring MVC为例,不使用Spring Boot 时,我们可能需要配置视图解析器,文件解析器, 请求适配器等等各种 Bean, 如果在使用数据库Redis,还需要配置数据库Redis相关Bean。

一、从@SpringBootApplication 启动注解入手

源码如下:

代码语言:javascript
复制
@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 {

	@AliasFor(annotation = EnableAutoConfiguration.class) 
	Class<?>[] exclude() default {}; 
	@AliasFor(annotation = EnableAutoConfiguration.class) 
	String[] excludeName() default {};

//根据包路径扫描 
	@AliasFor(annotation = ComponentScan.class, attribute = 
"basePackages") 
	String[] scanBasePackages() default {};

//直接根据 class 类扫描 
	@AliasFor(annotation = ComponentScan.class, attribute = 
"basePackageClasses") 
	Class<?>[] scanBasePackageClasses() default {}; 
}

初看@SpringBootApplication有很多的注解组成,其实归纳就是一个三体结构,重要的只有三个Annotation

  1. @Configuration(@SpringBootConfiguration实质就是一个@Configuration
  2. @EnableAutoConfiguration
  3. @ComponentScan

也就是说我们在开发的时候,加上上面的三个注解会等同于加上@SpringBootApplication 注解。

@Configuration 注解 :

这个注解实际上就是代表了一个配置类,相当于一个beans.xml文件 ;

@ComponentScan 注解:

@ComponentScan 的功能其实就是自动扫描并加载符合条件的组件或bean定义,最终将这些bean定义加载到容器中 ;

@EnableAutoConfiguration 注解:

spring中有关于@Enablexxx 的注解是开启某一项功能的注解,比如@EnableScheduling 表示开启spring 的定时任务。其原理是借助@Import的帮助,将所有符合自动配置条件的bean定义加载到Ioc容器; EnableAutoConfiguration代表开启springboot的自动装配。

代码语言:javascript
复制
@Target(ElementType.TYPE) 
@Retention(RetentionPolicy.RUNTIME) 
@Documented 
@Inherited 
@AutoConfigurationPackage 
@Import(AutoConfigurationImportSelector.class) 
public @interface EnableAutoConfiguration { 
	String ENABLED_OVERRIDE_PROPERTY = 
"spring.boot.enableautoconfiguration"; 
//按类型排序不需要自动装配的类 
	Class<?>[] exclude() default {}; 
//按名称排除不需要自动装配的类 
	String[] excludeName() default {}; 
}

从上面源码中我们可以知道,最关键的要属@Import(EnableAutoConfigurationImportSelector.class),借助 EnableAutoConfigurationImportSelector@EnableAutoConfiguration 可以帮助Spring Boot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot 创建并使用的IoC容器。同时借助于 Spring 框架原有的一个工具类:SpringFactoriesLoader@EnableAutoConfiguration 就可以实现智能的自动配置。

从这里可以看出该类实现很多的xxxAwareDeferredImportSelector,所有的aware都优先于selectImports;

方法执行,也就是说selectImports方法最后执行,那么在它执行的时候所有需要的资源都已经获取到了

代码语言:javascript
复制
public class AutoConfigurationImportSelector implements  
DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, 
BeanFactoryAware, EnvironmentAware, Ordered { 
... 
public String[] selectImports(AnnotationMetadata annotationMetadata) { 
	if (!this.isEnabled(annotationMetadata)) { 
	return NO_IMPORTS; 
} else { 
//1. 加载 META-INF/spring-autoconfigure-metadata.properties文件 
	AutoConfigurationMetadata autoConfigurationMetadata = 
AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);

//2. 获取注解的属性及其值(PS:注解指的是@EnableAutoConfiguration 注解) 
	AnnotationAttributes attributes = 
this.getAttributes(annotationMetadata);

//3. 在classpath下所有的 META-INF/spring.factories 文件中查找 
org.springframework.boot.autoconfigure.EnableAutoConfiguration 的值, 
并将其封装到一个 List 中返回 
	List<String> configurations =

this.getCandidateConfigurations(annotationMetadata, attributes); 
//4. 对上一步返回的 List 中的元素去重、排序 
	configurations = this.removeDuplicates(configurations);

//5.依据第 2 步中获取的属性值排除一些特定的类 
	Set<String> exclusions = 
this.getExclusions(annotationMetadata, attributes);

//6. 对上一步中所得到的 List 进行过滤,过滤的依据是条件匹配。这里用到的过滤器是org.springframework.boot.autoconfigure.condition.OnClassCondition最终返回的是一个 ConditionOutcome[]数组。
//(PS:很多类都是依赖于其它的类的,当有某个类时才会装配,所以这次过滤的就是根据是否有某个class进而决定是否装配的。这些类所依赖的类都写在META-INF/spring-autoconfigure-metadata.properties 文件里)

	this.checkExcludedClasses(configurations, exclusions); 
	configurations.removeAll(exclusions); 
	configurations = this.filter(configurations,autoConfigurationMetadata);
	this.fireAutoConfigurationImportEvents(configurations,exclusions); 
	return StringUtils.toStringArray(configurations); 
	} 
}

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { 
	List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderF actoryClass(), this.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; 
	} 
	
... 
}

SpringFactoriesLoader中加载配置, SpringFactoriesLoader 属于Spring框架私有的一种扩展方案,其主要功能就是从指定的配置文件META-INF/spring.factories加载配置, 即根据@EnableAutoConfiguration的完整类名org.springframework.boot.autoconfigure.EnableAutoConfiguration作为查找的 Key, 获取对应的一组@Configuration 类。

代码语言:javascript
复制
public abstract class SpringFactoriesLoader { 
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; 
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { 
	MultiValueMap<String, String> result = cache.get(classLoader); 
	if (result != null) 
	return result; 
	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 = 
		PropertiesLoaderUtils.loadProperties(resource); 
		for (Map.Entry<?, ?> entry : properties.entrySet()) { 
			List<String> factoryClassNames = Arrays.asList( 
		
		StringUtils.commaDelimitedListToStringArray((String) entry.getValue())); 
			result.addAll((String) entry.getKey(), factoryClassNames); 
		} 
	} 
		cache.put(classLoader, result); 
		return result; 
}

catch (IOException ex) { 
	throw new IllegalArgumentException("Unable to load  
	factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); 
	} 
}

...

总结:

@EnableAutoConfiguration作用就是从classpath中搜寻所有的META-INF/spring.factories 配置文件,并将其中org.springframework.boot.autoconfigure.EnableutoConfiguration 对应的配置项通过反射(Java Refletion)实例化为对应的标注了@Configuration 的JavaConfig形式的IoC容器配置类,然后汇总为一个并加载到IoC容器。

这些功能配置类要生效的话,会去classpath中找是否有该类的依赖类(也就是pom.xml 必须有对应功能的 jar 包才行)并且配置类里面注入了默认属性值类,功能类可以引用并赋默认值。生成功能类的原则是自定义优先,没有自定义时才会使用自动装配类。

所以功能类能生效需要的条件:

  1. spring.factories 里面有这个类的配置类(一个配置类可以创建多个围绕该功能的依赖类);
  2. pom.xml里面需要有对应的JAR包。

二、自动装配案例说明 Redis为例

1、从spring-boot-autoconfigure.jar/META-INF/spring.factories中获取Redis的相关配置类全限定名(有 120 多个的配置类)RedisAutoConfiguration, 一般一个功能配置类围绕该功能,负责管理创建多个相关的功能类,比如RedisAutoConfiguration负责:JedisConnectionFactoryRedisTemplateStringRedisTemplate 这3个功能类的创建spring.factories中的Redis配置类 。

2、RedisAutoConfiguration配置类生效的一个条件是在classpath路径下有RedisOperations 类存在,因此Spring Boot 的自动装配机制会去classpath下去查找对应的 class 文件。

代码语言:javascript
复制
@Configuration 
@ConditionalOnClass(RedisOperations.class) 
@EnableConfigurationProperties(RedisProperties.class) 
@Import({ LettuceConnectionConfiguration.class, 
JedisConnectionConfiguration.class }) 
public class RedisAutoConfiguration { 
...}

3、如果 pom.xml 有对应的 jar 包,就能匹配到对应依赖 class

代码语言:javascript
复制
<dependency> 
	<groupId>org.springframework.boot</groupId> 
	<artifactId>spring-boot-starter-data-redis</artifactId> 
</dependency>

4、匹配成功, 这个功能配置类才会生效, 同时会注入默认的属性配置类@EnableConfigurationProperties(RedisProperties.class)

代码语言:javascript
复制
@ConfigurationProperties(prefix = "spring.redis") 
	public class RedisProperties { 
	private int database = 0; 
	private String url; 
	private String host = "localhost"; 
	private String password; 
	private int port = 6379; 
...

5、Redis功能配置里面会根据条件生成最终的JedisConnectionFactoryRedisTemplate,并提供了默认的配置形式`@ConditionalOnMissingBean(name =

"redisTemplate") `

代码语言:javascript
复制
@Configuration 
@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(StringRedisTemplate.class) 
	public StringRedisTemplate stringRedisTemplate( 
		RedisConnectionFactory redisConnectionFactory) throws  
UnknownHostException { 
      StringRedisTemplate template = new StringRedisTemplate(); 
	  template.setConnectionFactory(redisConnectionFactory); 
      return template; 
	} 
}

6、最终创建好的默认装配类,会通过功能配置类里面的 @Bean注解,注入到IOC当中

7、用户使用,当用户在配置文件中自定义时候就会覆盖默认的配置@ConditionalOnMissingBean(name = "redisTemplate")

三、自动依赖过程总结

1、通过各种注解实现了类与类之间的依赖关系,容器在启动的时候Application.run,会调用EnableAutoConfigurationImportSelector.classselectImports 方法(其实是其父类的方法)

这里需要注意,调用这个方法之前发生了什么和是在哪里调用这个方法需要进一步的探讨。

2、selectImports方法最终会调用SpringFactoriesLoader.loadFactoryNames方法来获取一个全面的常用BeanConfiguration列表。

3、loadFactoryNames 方法会读取FACTORIES_RESOURCE_LOCATION(也就是 spring-boot-autoconfigure.jar 下面的 spring.factories),获取到所有的Spring 相关的 Bean 的全限定名 ClassName,大概 120 多个 。

4、 selectImports方法继续调用 filter(configurations, autoConfigurationMetadata);这个时候会根据这些BeanConfiguration里面的条件,来一一筛选,最关键的是@ConditionalOnClass,这个条件注解会去 classpath 下查找,jar 包里面是否有这个条件依赖类,所以必须有了相应的 jar 包,才有这些依赖类,才会生成 IOC 环境需要的一些默认配置Bean

5、最后把符合条件的BeanConfiguration 注入默认的EnableConfigurationPropertie类里面的属性值,并且注入到 IOC 环境当中。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-08-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 后场技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、从@SpringBootApplication 启动注解入手
  • 二、自动装配案例说明 Redis为例
  • 三、自动依赖过程总结
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档