1.SpringBoot自动装配原理 && Starter组件开发

178 阅读7分钟

一、自动装配的概念

自动装配,在我们的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方法,在此方法中:

  1. 执行prepareContext方法
  2. 执行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方法:

  1. 找到启动类的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));
			}

找到启动类

  1. 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;
	}

整个流程:

  1. SpringBoot启动时,实例化SpringApplication,实例化时进行启动类的Class对象的设置。然后执行SpringApplication的run方法,run方法中执行prepareContext方法和refreshContext方法。

  2. 接着执行prepareContext方法:注册启动类的BeanDefinition到Beanfactory中

  3. 然后执行refreshContext方法,在此方法中执行ConfigurationClassPostProcessor增强处理器的postProcessBeanDefinitionRegistry方法

  4. 在postProcessBeanDefinitionRegistry方法中调用ConfigurationClassParser的parse方法,parse方法主要两件事:1.解析启动类上面的@Import注解,并实例化注解中的Selector类,2.由Selector类获取Configuration配置类,Selector类调用SpringFactoriesLoader类加载classpath路径下的META-INF/spring.factories文件,得到需要自动装配的Configuration配置类。

  5. 最后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