Fork me on GitHub

SpringBoot原理分析 - 自动装配

目录

SpringBoot解决了spring以及springmvc繁琐的配置的痛点,以“约定大于配置”为原则,实现了自动装配。下面来探究下SpringBoot自动装配原理。

一、何为装配

把bean放入到Spring的Ioc容器叫做装配,那么在装配Bean的时候,我们首先要知道哪些类需要被装配,实现这一方式的途径总体上说分为两种,一种是传统的xml方式,另一种则是注解方式。下面介绍下通过注解来实现装配。

启动原理

我们发现任何一个springboot的项目都会有如下一个启动类:

1
2
3
4
5
6
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}

为了了解SpringBoot原理,我们直接从Annotation 入 手,看看@SpringBootApplication里面,做了什么? 打开SpringBootApplication这个注解,可以看到它实际上 是一个复合注解

1
2
3
4
5
6
7
8
9
10
11
@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本质上是由3个注解组成,分别是

  1. @Configuration (@SpringBootConfiguration里面也用的@Configuration)
  2. @EnableAutoConfiguration
  3. @ComponentScan

我们可以直接用这三个注解也可以启动 springboot 应用, 只是每次配置三个注解比较繁琐,所以直接用一个复合注 解更方便些。 然后仔细观察者三个注解,除了EnableAutoConfiguration 可能稍微陌生一点,其他两个注解使用得都很多 。

@Configuration

Configuration这个注解大家应该有用过,它是JavaConfig形式的基于Spring IOC容器的配置类使用的一种注解。因为 SpringBoot 本质上就是一个 spring 应用,所以通过这个注解来加载IOC容器的配置是很正常的。所以在启动类 里面标注了@Configuration,意味着它其实也是一个 IoC 容器的配置类。

传统意义上的 spring 应用都是基于 xml 形式来配置 bean 的依赖关系。然后通过spring容器在启动的时候,把bean进行初始化并且,如果bean之间存在依赖关系,则分析这些已经在IoC容器中的bean根据依赖关系进行组装。 直到 Java5 中,引入了 Annotations 这个特性,Spring 框架也紧随大流并且推出了基于 Java 代码和Annotation元信息的依赖关系绑定描述的方式,也就是JavaConfig。

从spring3开始,spring就支持了两种bean的配置方式, 一种是基于xml文件方式、另一种就是JavaConfig 。

任何一个标注了@Configuration 的 Java 类定义都是一个 JavaConfig 配置类。而在这个配置类中,任何标注了 @Bean 的方法,它的返回值都会作为 Bean 定义注册到 Spring的IOC容器,方法名默认成为这个bean的id

@ComponentScan

@ComponentScan这个注解是大家接触得最多的了,相当于 xml 配置文件中的<context:component-scan />。 它的主要作用就是扫描指定路径下的标识了需要装配的类,自动装配到spring的Ioc容器中。

标识需要装配类的形式主要是:@Component、@Repository、@Service、@Controller这类的注解标识的类;

ComponentScan 默认会扫描当前package 下的的所有加了相关注解标识的类到IoC容器中。

@EnableAutoConfiguration

Enable 并不是新鲜玩意

在 spring3.1 版本中,提供了一系列的@Enable 开 头的注解,Enable主机应该是在JavaConfig框架上更进一步的完善,是的用户在使用spring相关的框架是,避免配置大量的代码从而降低使用的难度 。

比如@EnableScheduling、@EnableCaching、@EnableWebMvc等,@EnableAutoConfiguration的理念和做事方式其实一脉相承,简单概括一下就是,借助@Import的支持,收集和注册特定场景相关的bean定义。

  • @EnableWebMvc,这个注解引入了MVC 框架在Spring 应用中需要用到的所有 bean
  • @EnableScheduling是通过@Import将Spring调度框架相关的bean定义都加载到IoC容器,开启计划任务的支持。

而@EnableAutoConfiguration也是借助@Import的帮助,将所有符合条件的@Configuration 配置都加载到当前SpringBoot创建并使用的IoC容器中。仅此而已!

@EnableAutoConfiguration会根据类路径中的jar依赖为项目进行自动配置,如:添加了spring-boot-starter-web依赖,会自动添加Tomcat和Spring MVC的依赖,Spring Boot会对Tomcat和Spring MVC进行自动配置。

image-20200605163703518

@EnableAutoConfiguration作为一个复合Annotation,其自身定义关键信息如下:

1
2
3
4
5
6
7
8
9
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
...
}

@Import(AutoConfigurationImportSelector.class)从名字来看,可以猜到它是基于ImportSelector来实现基于动态bean的加载功能。要知道Springboot @Enable*注解的工作原理ImportSelector接口selectImports返回的数组(类的全类名)都会被纳入到 spring容器中。 那么可以猜想到这里的实现原理也一定是一样的,定位到 AutoConfigurationImportSelector这个类中的 selectImports方法 。

本质上来说,其实EnableAutoConfiguration会帮助 springboot应用把所有符合@Configuration配置都加载到当前SpringBoot创建的IoC容器,而这里面借助了Spring框架提供的一个工具类SpringFactoriesLoader的支持。以及用到了Spring提供的条件注解 @Conditional,选择性的针对需要加载的bean进行条件过滤

AutoConfigurationImportSelector

我们来看下AutoConfigurationImportSelector源码下的selectImports方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
//加载spring-autoconfigure-metadata.properties配置文件中的数据
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
//通过getAutoConfigurationEntry获取需要动态加载的class
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
//返回需要交给spring进行注入的类
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

AutoConfigurationMetadataLoader 的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
final class AutoConfigurationMetadataLoader {

protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties";

private AutoConfigurationMetadataLoader() {
}

public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
return loadMetadata(classLoader, PATH);
}

static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
try {
Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
: ClassLoader.getSystemResources(path);
Properties properties = new Properties();
while (urls.hasMoreElements()) {
properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
}
return loadMetadata(properties);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
}
}

static AutoConfigurationMetadata loadMetadata(Properties properties) {
return new PropertiesAutoConfigurationMetadata(properties);
}

/**
* {@link AutoConfigurationMetadata} implementation backed by a properties file.
*/
private static class PropertiesAutoConfigurationMetadata implements AutoConfigurationMetadata {

private final Properties properties;

PropertiesAutoConfigurationMetadata(Properties properties) {
this.properties = properties;
}

@Override
public boolean wasProcessed(String className) {
return this.properties.containsKey(className);
}

@Override
public Integer getInteger(String className, String key) {
return getInteger(className, key, null);
}

@Override
public Integer getInteger(String className, String key, Integer defaultValue) {
String value = get(className, key);
return (value != null) ? Integer.valueOf(value) : defaultValue;
}

@Override
public Set<String> getSet(String className, String key) {
return getSet(className, key, null);
}

@Override
public Set<String> getSet(String className, String key, Set<String> defaultValue) {
String value = get(className, key);
return (value != null) ? StringUtils.commaDelimitedListToSet(value) : defaultValue;
}

@Override
public String get(String className, String key) {
return get(className, key, null);
}

@Override
public String get(String className, String key, String defaultValue) {
String value = this.properties.getProperty(className + "." + key);
return (value != null) ? value : defaultValue;
}

}

}

上述selectImports方法就返回了需要springboot自动装配的一些bean,通过String[]的形式返回需要装配的bean的name,但是这个方法的在真正返回需要装配的bean的name之前,还做了很多操作。做了些动态过滤的操作。

第一步是通过loadMetadata加载当前classpath下的spring-autoconfigure-metadata.properties文件,这个文件里面配置了所有动态加载的条件。第二步是通过getAutoConfigurationEntry获取需要动态加载的class,这一步具体源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//下面这一步是去加载classpath下的spring.factories文件中的实例
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}

getCandidateConfigurations这个详细源码如下:

1
2
3
4
5
6
7
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;
}

Spring 工厂加载机制

Spring 工厂加载机制,即 Spring Factories Loader,核心逻辑是使用 SpringFactoriesLoader加载由用户实现的类,并配置在约定好的META-INF/spring.factories 路径下,该机制可以为框架上下文动态的增加扩展。
该机制类似于 Java SPI,给用户提供可扩展的钩子,从而达到对框架的自定义扩展功能。

这里SpringFactoriesLoader 的作用就是从classpath/META-INF/spring.factories文件中,根据key来 加载对应的类到spring IoC容器中。

可以看出这里就是加载当前classpath下的所有的spring.factories文件中的内容。下面就是spring.factories中EnableAutoConfiguration的配置。这些如果没有AutoConfigurationImportSelector的过滤操作,这里所有配置的值,都会在getCandidateConfigurations方法中会返回给IOC容器,springboot会自动装载这些类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\

SpringBoot项目启动时,就是加载上述XXAutoConfiguration从而实现自动装配。

总结:

@EnableAutoConfiguration作用就是从classpath中搜寻所有的META-INF/spring.factories配置文件,并将其中org.springframework.boot.autoconfigure.EnableutoConfiguration对应的配置项通过反射实例化为对应的标注了@Configuration的JavaConfig形式的IoC容器配置类,然后汇总为一个并加载到IoC容器。这些功能配置类要生效的话,会去classpath中找是否有该类的依赖类(也就是pom.xml必须有对应功能的jar包才行)并且配置类里面注入了默认属性值类,功能类可以引用并赋默认值。生成功能类的原则是自定义优先,没有自定义时才会使用自动装配类。

相关文章