SpringBoot作为现在Java微服务开发中的中流砥柱,其开箱即用、免去xml配置等特点,高效便捷的使Java程序员进行业务开发。
每次询问SpringBoot相关技术点时,其自动装配原理也是重点关注对象。所以通过源码分析其原理对于SpringBoot学习还是非常重要的。
下面我将通过源码分析出我理解的自动装配原理过程,如有分析不对的地方希望大家给予指正。
什么是自动装配?
使用过Spring整合SSM框架的程序员,一定有过被繁杂的XML配置项弄的头疼不已。
比如在Spring.xml 里面要配置数据源、Mybatis的Bean工厂、组件扫描,在SpringMVC.xml中配置视图解析器、首页视图控制、默认处理静态资源规则、MVC注解支持,在web.xml中配置编码过滤器(CharacterEncodingFilter
)、MVC前端控制器(DispatcherServlet
)、文件上传过滤器、RestFul 风格过滤器等等配置。
但是在SpringBoot项目中,只需导入相关依赖,而且无需任何配置就可以通过Main
方法成功启动项目。
@SpringBootApplication
public class MyAppApplication {
public static void main(String[] args) {
SpringApplication.run(MyAppApplication.class, args);
}
}
得益于自动装配,在SpringBoot中我们不需要手动配置,只需要引入相关的starter依赖即可开箱即用。比如想要开发Web项目,只需要导入对应web的starter即可
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
DispatcherServlet
通过返回IOC容器,可以看到我们无需任何配置,SpringBoot在加载时就替我们注册了相应开发的组件。
如何实现的自动装配?
我们首先来观察下启动类,这里有一个核心注解@SpringBootApplication
。
@SpringBootApplication // 核心
public class MyAppApplication {
public static void main(String[] args) {
SpringApplication.run(MyAppApplication.class, args);
}
}
@SpringBootApplication
...
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(···)
public @interface SpringBootApplication {...}
可以发现@SpringBootApplication
是由另外三个核心注解@SpringBootConfiguration
、@EnableAutoConfiguration
、@ComponentScan(···)
组成。
@SpringBootConfiguration
...
@Configuration
public @interface SpringBootConfiguration {...}
也即@SpringBootApplication
本质也是一个Configuration配置类。
@ComponentScan
包扫描注解,默认扫描当前启动类路径下所有同级以及子级路径下,被@Component
(@Service
,@Controller
、@Repository
)注解标记的类。
@EnableAutoConfiguration
...
@AutoConfigurationPackage // 主启动类下所有组件注册到IOC容器中
@Import(AutoConfigurationImportSelector.class) // 加载自动装配类
public @interface EnableAutoConfiguration {}
该注解是实现自动装配的核心,其下又由两个分注解@AutoConfigurationPackage
、@Import(AutoConfigurationImportSelector.class)
组成
@AutoConfigurationPackage
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {}
可以看到该注解又通过@Import
注解导入了一个AutoConfigurationPackages.Registrar.class
类。
AutoConfigurationPackages.Registrar
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 核心注册方法
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
...
}
通过调试registerBeanDefinitions
方法,它调用register
将当前层级下(主启动类)所有的组件进行批量导入注册。
当前层级
在BeanDefinition中需要注册的Bean信息
@Import(AutoConfigurationImportSelector.class)
自动装配核心功能实际就由@Import
导入的 AutoConfigurationImportSelector
类来实现的。
selectImports
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// 获取所有需要装配的Bean
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
观察源码发现selectImports()
方法返回了一个String数组,它的作用就是获取所有符合条件的类并装载到IOC容器中,然后返回一个装配类全类名名字的String数组。
这里继续往下调试,发现是利用getAutoConfigurationEntry(annotationMetadata)
方法给IoC容器中批量导入组件
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 获取所有候选的配置
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 按需加载
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
通过getCandidateConfigurations()
方法我们可以获取到所有需要导入到容器中的配置类。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 通过工厂方法加载组件
List<String> configurations = new ArrayList<>(
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
然后我们会看到方法调用了Spring的工厂加载器的loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader())
方法,显然这是通过工厂加载得到所有的组件。
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>>
loadSpringFactories(ClassLoader classLoader) {
// 优先从本地缓存中寻找所有配置类
Map<String, List<String>> result = (Map)cache.get(classLoader);
if (result != null) {
return result;
} else {
...
try {
// 读取META-INF/spring.factories文件寻找所有配置类
Enumeration urls = classLoader.getResources("META-INF/spring.factories");
...
}
最后从loadSpringFactories()
方法中可以看出,先去本地缓存中寻找需要加载的配置类的信息,如果缓存中没有就会从所有包下含有META-INF/spring.factories
的配置文件中寻找配置类的信息。
其中在spring-boot-autoconfigure-2.4.11.jar包里面的META-INF/spring.factories
文件存放着所有需要自动装配的类的信息。其数量正好对应上图configurations的144个配置项。
按需加载
虽然144个全场景配置项的自动配置启动的时候默认全部加载(xxxxAutoConfiguration)。但实际经过后续处理后只剩下23个配置项真正加载进来。很明显,SpringBoot只会加载实际你要用到的场景中的配置类。这是如何做到的了?
这里我们选取一个自动配置类,观察到每一个自动配置类都有着@Conditional
或者其派生条件注解。
-
@ConditionalOnBean
:当容器里有指定Bean的条件下。 -
@ConditionalOnMissingBean
:当容器里没有指定Bean的情况下。 -
@ConditionalOnSingleCandidate
:当指定Bean在容器中只有一个,或者虽然有多个但是指定首选Bean。 -
@ConditionalOnClass
:当类路径下有指定类的条件下。 -
@ConditionalOnMissingClass
:当类路径下没有指定类的条件下。 -
@ConditionalOnProperty
:指定的属性是否有指定的值。 -
@ConditionalOnResource
:类路径是否有指定的值。 -
@ConditionalOnExpression
:基于SpEL表达式作为判断条件。 -
@ConditionalOnJava
:基于Java版本作为判断条件。 -
@ConditionalOnJndi
:在JNDI存在的条件下差在指定的位置。 -
@ConditionalOnNotWebApplication
:当前项目不是Web项目的条件下。 -
@ConditionalOnWebApplication
:当前项目是Web项目的条件下。
RedisAutoConfiguration
@Configuration(
proxyBeanMethods = false
)
// 检查是否有该类才会进行加载
@ConditionalOnClass({RedisOperations.class})
// 绑定默认配置信息
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
public RedisAutoConfiguration() {
}
...
}
所以每一个自动配置类当满足条件注解的条件时,该类才会生效进行自动装配的处理流程。
总结
SpringBoot的自动装配原理就是通过Spring的工厂加载器的loadSpringFactories()
方法加载到META-INF/spring.factories
中所有的自动装配类的信息去实现自动装配。再通过每一个自动装配类的条件注解进行按需加载,只加载每一个满足注解条件生效后的配置类。