一、自动装配的概念
自动装配,在我们的SpringBoot项目开发中非常常见。其实就是自动将Bean注入到Spring的IoC容器中,然后就可以通过@Resource等注解获取IoC容器中的Bean来使用。
举个日常集成Redis的例子,引入Redis starter依赖(SpringBoot会对Redis starter组件相关的Bean进行自动装配到IoC容器),设置redis配置,然后就可以从IoC容器中获取start组件相关的RedisTemplate这个bean,基于这个RedisTemplate开发我们的业务逻辑。如下是具体操作过程:
- 引入redis starter依赖
<!--引入Redis的starter依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 添加redis配置
spring:
# redis配置
redis:
host: 127.0.0.1
port: 6379
password:
database: 0
- 我们就可以获取RedisTemplate这个bean来操作redis了
@RestController
public class TestController {
/**
* 从IoC容器获取RedisTemplate这个bean
*/
@Resource
RedisTemplate<String, String> redisTemplate;
@GetMapping("/testRedis")
public String doTest() {
redisTemplate.opsForValue().set("key", "value");
return "OK";
}
}
由此可见,通过引入redis starter依赖就自动装配这个start组件相关的RedisTemplate这个bean到IoC容器中了,业务逻辑中就可以直接基于@Resource等注解获取到这个Bean进行自己的业务逻辑开发。
二、自动装配的原理
那么,我们来看看SpringBoot自动装配的基本原理是怎样的。
2.1 SpringBoot启动会执行SpringApplication实例化
首先 SpringBoot启动会执行SpringApplication实例化
@SpringBootApplication
public class SpringBootLearnApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootLearnApplication.class);
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
主要是设置启动类SpringBootLearnApplication的Class对象
注意:其他相关的代码我们,省略不再详细说明,我们只看核心流程。
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
...
// 设置启动类的Class对象
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
...
this.mainApplicationClass = deduceMainApplicationClass();
}
设置启动类SpringBootLearnApplication的Class对象
接着SpringBoot启动会执行run方法,在此方法中:
- 执行prepareContext方法
- 执行refreshContext方法
我们分别来分析这两个方法
2.2 执行prepareContext方法
public ConfigurableApplicationContext run(String... args) {
...
// 创建Context上下文,并把启动类对应的BeanDefinition对象加载到Context容器上下文中
context = createApplicationContext();
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 执行refresh刷新方法
refreshContext(context);
}
我们先看prepareContext方法,这个方法主要是注册启动类的BeanDefinition对象到Context容器
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
...
// 获取之前设置的启动类
Set<Object> sources = getAllSources();
// 执行load方法,这个load方法实际上就是注册了启动类的相关BeanDefinition对象
load(context, sources.toArray(new Object[0]));
}
获取之前设置的启动类
public Set<Object> getAllSources() {
Set<Object> allSources = new LinkedHashSet<>();
// this.primarySources中就是之前设置的启动类
if (!CollectionUtils.isEmpty(this.primarySources)) {
allSources.addAll(this.primarySources);
}
...
return Collections.unmodifiableSet(allSources);
}
BeanDefinitionLoader.load方法来加载
protected void load(ApplicationContext context, Object[] sources) {
...
// 由BeanDefinitionLoader来执行加载
BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
loader.load();
}
由BeanDefinitionLoader来执行加载
实际上就是加载启动类
public int load() {
int count = 0;
for (Object source : this.sources) {
count += load(source);
}
...
}
加载启动类
执行注册
private int load(Class<?> source) {
...
// 执行注册
this.annotatedReader.register(source);
}
由annotatedReader执行注册
public void register(Class<?>... annotatedClasses) {
for (Class<?> annotatedClass : annotatedClasses) {
registerBean(annotatedClass);
}
主要是封装启动类的BeanDefinition对象,注册到Spring容器
<T> void doRegisterBean(Class<T> annotatedClass, @Nullable Supplier<T> instanceSupplier, @Nullable String name,
@Nullable Class<? extends Annotation>[] qualifiers, BeanDefinitionCustomizer... definitionCustomizers) {
// 把启动类封装为BeanDefinition对象,并注册到Context上下文中的BeanFactory的Map属性中
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
}
把启动类的BeanDefinition对象,注册到Context上下文中的BeanFactory的Map属性中
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
...
// Still in startup registration phase
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
removeManualSingletonName(beanName);
}
2.2 执行refreshContext方法
回到run方法,接着执行refreshContext方法
public ConfigurableApplicationContext run(String... args) {
...
// 加载启动类的beanDefinition对象
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 执行refresh刷新方法
refreshContext(context);
...
}
Context上下文执行刷新
protected void refresh(ApplicationContext applicationContext) {
...
((AbstractApplicationContext) applicationContext).refresh();
抽象类AbstractApplicationContext执行refresh方法
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext {
@Override
public void refresh() throws BeansException, IllegalStateException {
...
invokeBeanFactoryPostProcessors(beanFactory);
}
调用ConfigurationClassPostProcessor配置类增强处理器的相关方法
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
...
// 从BeanFactory中获取配置处理器name,添加配置类处理器
String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
for (String ppName : postProcessorNames) {
currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
// 调用ConfigurationClassPostProcessor
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry)
}
Configuration配置类处理器
然后执行invokeBeanDefinitionRegistryPostProcessors方法,此方法中会调用ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法
postProcessor.postProcessBeanDefinitionRegistry(registry);
postProcessBeanDefinitionRegistry方法:
- 找到启动类的BeanDefinition
// 找到启动类
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
String[] candidateNames = registry.getBeanDefinitionNames();
for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
找到启动类
- ConfigurationClassParser解析启动类上面的注解
主要解析@EnableAutoConfiguration注解中的@Import注解中的AutoConfigurationImportSelector类
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
// ConfigurationClassParser解析启动类上面的注解
parser.parse(candidates);
ConfigurationClassParser {
public void parse(Set<BeanDefinitionHolder> configCandidates) {
// 解析启动类上面的@Import注解
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
// 由@Import注解中的Selector类找到Configuration配置类
this.deferredImportSelectorHandler.process();
}
}
ConfigurationClassParser.parse最终调用processImports方法
// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), true);
我们看看getImports方法
private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
Set<SourceClass> imports = new LinkedHashSet<>();
Set<SourceClass> visited = new LinkedHashSet<>();
collectImports(sourceClass, imports, visited);
collectImports,主要就是递归查找启动类上面的@Import注解
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
throws IOException {
if (visited.add(sourceClass)) {
for (SourceClass annotation : sourceClass.getAnnotations()) {
String annName = annotation.getMetadata().getClassName();
if (!annName.equals(Import.class.getName())) {
collectImports(annotation, imports, visited);
}
}
imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
}
}
看到找到了Selector注解类
找到了Selector注解类
解析之后实例化Selector对象,然后回到ConfigurationClassParser类parse方法中的下面逻辑
this.deferredImportSelectorHandler.process();
public void process() {
List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
this.deferredImportSelectors = null;
DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler()
...
deferredImports.forEach(handler::register);
handler.processGroupImports();
}
public void processGroupImports() {
for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
grouping.getImports().forEach(entry -> {
ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
public Iterable<Group.Entry> getImports() {
for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
this.group.process(deferredImport.getConfigurationClass().getMetadata(),
deferredImport.getImportSelector());
}
return this.group.selectImports();
}
由Selector类获取Configuration配置类
AutoConfigurationImportSelector.getAutoConfigurationEntry方法
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
...
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
调用AutoConfigurationImportSelector.getAutoConfigurationEntry方法
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
...
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
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;
}
SpringFactoriesLoader.loadFactoryNames方法
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
SpringFactoriesLoader类加载特定目录文件,即classpath路径下的META-INF/spring.factories,得到需要自动装配的Configuration配置类。
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()) {
String factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
看到各种配置类
这个其中就有我们的redis自动配置类
redis自动配置类
2.3 配置类声明的Bean装配到IoC容器
既然找到了自动配置类,那么后续Spring容器就会自动把Configuration配置类中Bean自动装配到容器,然后我们就可以从IoC容器之中获取Bean实例并使用。例如RedisTemplate
*/
@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;
}
整个流程:
-
SpringBoot启动时,实例化SpringApplication,实例化时进行启动类的Class对象的设置。然后执行SpringApplication的run方法,run方法中执行prepareContext方法和refreshContext方法。
-
接着执行prepareContext方法:注册启动类的BeanDefinition到Beanfactory中
-
然后执行refreshContext方法,在此方法中执行ConfigurationClassPostProcessor增强处理器的postProcessBeanDefinitionRegistry方法
-
在postProcessBeanDefinitionRegistry方法中调用ConfigurationClassParser的parse方法,parse方法主要两件事:1.解析启动类上面的@Import注解,并实例化注解中的Selector类,2.由Selector类获取Configuration配置类,Selector类调用SpringFactoriesLoader类加载classpath路径下的META-INF/spring.factories文件,得到需要自动装配的Configuration配置类。
-
最后Configuration配置类中的bean装配到IoC容器中,最后就可以获取这个bean来执行相应的业务逻辑。
三、开发一个Starter组件
我们开发一个自定义线程池的Starter组件,其他工程可以引入这个组件,实现调用组件的相关功能。
3.1 创建一个新的工程demo-spring-boot-starter
3.2 引入SpringBoot基础依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
3.3 创建自动配置类,以此装配线程池bean到容器
@Configuration
public class ThreadPoolAutoConfiguration {
@Bean
@ConditionalOnClass(ThreadPoolExecutor.class)
public ThreadPoolExecutor ThreadPool() {
return new ThreadPoolExecutor(15, 25,10, TimeUnit.SECONDS, new ArrayBlockingQueue<>(200));
}
}
3.4 resource包下面创建META-INF/spring.factories文件
在此文件中指定自动配置类的全路径名称
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.example.configuration.ThreadPoolAutoConfiguration
maven clean && install 打包starter
3.5 其他工程中引入线程池starter
<!--引入自定义starter-->
<dependency>
<groupId>org.example</groupId>
<artifactId>demo-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
调用线程池组件
@SpringBootTest
@RunWith(SpringRunner.class)
@Slf4j
public class MyTest {
@Resource
private ThreadPoolExecutor DemoThreadPool;
@Test
public void testThreadPool() {
log.info("coreSize:{}", DemoThreadPool.getCorePoolSize());
}
}
看到打印结果::coreSize:15, 说明正确调用了我们自定义的线程池starter组件功能了。
2024-10-26 16:59:39.015 INFO 10184 --- [ main] org.example.MyTest : coreSize:15