【Spring Boot】原理

本文先通过介绍Bean的不同加载方式引出Spring Boot中的自动装配原理。文中的图片内容参考了黑马程序员在B站上的内容。

Bean的加载方式

1. XML+ < bean/ >

2.XML: context + 注解(@Component)

3.配置类+扫描+注解(@Component)

  • @Bean定义FactoryBean接口
  • @ImportResource
  • @Configuration注解的proxyBeanMethods属性

4.@Import导入Bean的类

  • @Import导入配置类

5.AnnotationConfigApplicationContext调用register方法

6.@Import导入ImportSelector接口

7.@Import导入ImportBeanDifinitionRegistrar接口

8.@Import导入BeanDefinitionRegistryPostProcessor接口

最后对容器中的Bean进行干预,其能影响最终结果。

Bean的加载控制

  • Bean的加载控制根据特定情况对Bean进行选择性加载。

Bean的依赖属性配置

通过读取properties来读取属性。

@EnableConfigurationProperties(**.class)

这个注解可以将读取数据的类加载为Bean也就是需要在对应的类上加@Component注解。

自动配置原理

1.收集Spring开发者的编程习惯,整理开发过程使用的常用技术列表(技术集A)

2.收集常用技术的使用参数(技术集A),整理开发过程中每一个技术的常用设置列表(设置集B)

3.初始化SpringBoot基础环境,加载用户自定义的Bean和导入其他坐标,形成初始环境。

4.将技术集A包含的所有技术都定义出来,在Spring启动时默认全部加载

5.将技术集A中具有使用条件的技术约定出来,设置成按照条件加载,由开发者决定是否使用该技术(与初始环境对比)

6.将设置集B作为默认配置加载(约定大于配置),减少开发者配置工作量。

7.开放设置集B的配置覆盖接口,由开发者根据自身需要度额定是否覆盖默认配置。

自动配置原理-源码

首先从@SpringBootApplication注解开始,这是一个组合注解,其中包含了很多注解。

主要的注解包括了下面三个:

@SpringBootConfiguration

@EnableAutoConfiguration

@ComponentScan(excludeFilters = {

@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),

@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

第一个:@SpringBootConfiguration

@Configuration

@Component

@Indexed:主要在SpringBoot启动期加速。

第二个:@EnableAutoConfiguration

@AutoConfigurationPackage

重点 @Import(AutoConfigurationPackages.Registrar.class)
重点@Import(AutoConfigurationImportSelector.class)

AutoConfigurationPackages.Registrar 实现了 ImportBeanDefinitionRegistrar

这一段的功能主要是 生成了一个名为 包路径 的Bean

java 复制代码
		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
			//扫描当前包的路径
			register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
		}

		@Override
		public Set<Object> determineImports(AnnotationMetadata metadata) {
			return Collections.singleton(new PackageImports(metadata));
		}

AutoConfigurationImportSelector实现了很多接口,主要分为三类

第一类是 DeferredImportSelector 继承自 ImportSelector

第二类是 注入资源 Aware相关 BeanClassLoaderAware,

ResourceLoaderAware, BeanFactoryAware, EnvironmentAware

通过实现Aware接口可以方便的实现对于拿取容器中的资源。

第三类是 Ordered 当前类在Spring容器中的加载顺序。

java 复制代码
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered 

第一个类 DeferredImportSelector 继承了 ImportSelector 并且推迟加载

ImportSelector中的需要实现如下抽象方法:

java 复制代码
	String[] selectImports(AnnotationMetadata importingClassMetadata);

Deferred中还定义了一个Group的接口:其中的核心是process方法

java 复制代码
		public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
			Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
					() -> String.format("Only %s implementations are supported, got %s",
							AutoConfigurationImportSelector.class.getSimpleName(),
							deferredImportSelector.getClass().getName()));
			AutoConfigurationImportSelector autoConfigurationImportSelector = (AutoConfigurationImportSelector) deferredImportSelector;
			AutoConfigurationReplacements autoConfigurationReplacements = autoConfigurationImportSelector
				.getAutoConfigurationReplacements();
			Assert.state(
					this.autoConfigurationReplacements == null
							|| this.autoConfigurationReplacements.equals(autoConfigurationReplacements),
					"Auto-configuration replacements must be the same for each call to process");
			this.autoConfigurationReplacements = autoConfigurationReplacements;
			AutoConfigurationEntry autoConfigurationEntry = autoConfigurationImportSelector
				.getAutoConfigurationEntry(annotationMetadata);
			this.autoConfigurationEntries.add(autoConfigurationEntry);
			for (String importClassName : autoConfigurationEntry.getConfigurations()) {
				this.entries.putIfAbsent(importClassName, annotationMetadata);
			}
		}

上方代码中:

java 复制代码
			AutoConfigurationEntry autoConfigurationEntry = autoConfigurationImportSelector.getAutoConfigurationEntry(annotationMetadata);
java 复制代码
	protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		// 获取元注解的的所有属性
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		// 元注解:exclude 和 excludeName : 在技术集A中不需要的配置在这里;也就是获取技术集A中排除掉exclude中的其他配置作为候选①
		List<String> configurations = getCandidateConfigurations(annotationMextadata, 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方法的实现如下:

java 复制代码
	public static ImportCandidates load(Class<?> annotation, @Nullable ClassLoader classLoader) {
		Assert.notNull(annotation, "'annotation' must not be null");
		ClassLoader classLoaderToUse = decideClassloader(classLoader);
		String location = String.format(LOCATION, annotation.getName());
		Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
		List<String> importCandidates = new ArrayList<>();
		while (urls.hasMoreElements()) {
			URL url = urls.nextElement();
			importCandidates.addAll(readCandidateConfigurations(url));
		}
		return new ImportCandidates(importCandidates);

在这里需要注意:低版本的Spring Boot在自动加载时的机制是扫描FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories" 下的所有包。

但是在Spring Boot 3.x及之后的版本中已经移除,修改为LOCATION = "META-INF/spring/%s.imports"

在程序运行后 %s 会被替换,

默认路径为:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

在文件夹中可以看到其默认配置:

这里 相比于之前使用的spring.factories 少了很多个autoConfiguration

java 复制代码
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration
org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration

当更新坐标之后:假设添加Redis的坐标:

xml 复制代码
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

添加后:

其中的META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 就会被添加候选配置中:

但是在这里也需要注意:有以下的几个注解,@ConditionalOnClass中对应的类存在才会加载对应的技术集到自动配置当中,

java 复制代码
@AutoConfiguration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(DataRedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public final class DataRedisAutoConfiguration {
...
}

如何变更自动配置

老版本变更自动配置:

新版本变更自动配置:

需要在对应的 META-INF/spring文件夹下新建一个名称为:

org.springframework.boot.autoconfigure.AutoConfiguration.imports 的文件,在文件中标明需要自动装配的类即可。

比如这样:edu.neusoft.learn.bean.CartoonCatAndMouse

其中还涉及到一些注解的解释:

@ConfigurationProperties(prefix = "cartoon") 这个注解配置在属性类上,其中的前缀对应的在配置文件中的前缀,然后之后就按照属性名进行配置即可。

@EnableConfigurationProperties(CartoonProperties.class)这个注解是配置在需要将当前的属性注入到当前的类的单例对象当中。

如果不走自动配置,也就是如果不在imports文件中手写对应类名,就需要使用@Import(CartoonCatAndMouse.class)手动导入。

@ConditionalOnClass(Cat.class)这个注解一般加在需要自动装配的类上,用以当前装配的条件,只有某个对应的类文件存在,那么才自动配置当前的bean。

相关推荐
翊谦6 小时前
Java Agent开发 Milvus 向量数据库安装
java·数据库·milvus
晓晓hh6 小时前
JavaSE学习——迭代器
java·开发语言·学习
查古穆6 小时前
栈-有效的括号
java·数据结构·算法
Java面试题总结7 小时前
Spring - Bean 生命周期
java·spring·rpc
硅基诗人7 小时前
每日一道面试题 10:synchronized 与 ReentrantLock 的核心区别及生产环境如何选型?
java
014-code7 小时前
String.intern() 到底干了什么
java·开发语言·面试
摇滚侠7 小时前
JAVA 项目教程《苍穹外卖-12》,微信小程序项目,前后端分离,从开发到部署
java·开发语言·vue.js·node.js
楚国的小隐士8 小时前
为什么说Rust是对自闭症谱系人士友好的编程语言?
java·rust·编程·对比·自闭症·自闭症谱系障碍·神经多样性
春花秋月夏海冬雪8 小时前
代码随想录刷题 - 贪心Part1
java·算法·贪心·代码随想录
野生技术架构师8 小时前
2026年牛客网最新Java面试题总结
java·开发语言