自动装配机制
在传统的 Spring 框架中,开发者需要通过 XML
文件或 Java
配置类显式地声明 Bean 和各种配置项(例如数据源、事务管理、视图解析器等)。Spring Boot 的自动装配旨在减少这些繁琐的配置,通过默认的配置和条件装配,自动完成很多配置工作,从而减少开发者的配置量。
@SpringBootApplication注解
可以把 @SpringBootApplication
注解看作是 @SpringBootConfiguration
、@EnableAutoConfiguration
、@ComponentScan
注解的集合。
-
@EnableAutoConfiguration
:自动配置机制的核心注解。 -
@SpringBootConfiguration
:作为@Configuration
注解的扩展,它标识该类为 Spring 的配置类。在该类中可以定义@Bean
方法,这些方法返回的对象将被注册到 Spring 容器中,由容器管理其生命周期。 -
@ComponentScan
:从声明@SpringBootApplication
的类所在的包开始,自动扫描并注册@Component
、@Service
、@Repository
等注解的类到 Spring 容器中。它确保应用程序的组件、服务、控制器等能够被自动发现和注入。
@EnableAutoConfiguration核心注解
@EnableAutoConfiguration
通过 @Import
注解导入了 AutoConfigurationImportSelector
类。
@Import
注解的作用是将指定的配置类或 ImportSelector
导入到当前的配置类中。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
AutoConfigurationImportSelector核心类
AutoConfigurationImportSelector
是自动装配的核心类,其实现了ImportSelector
接口的 selectImports
方法。
selectImports()方法
该方法主要有两个作用:
- 判断自动装配开关是否打开,检查 yml 文件是否修改了
spring.boot.enableautoconfiguration=false
- 调用
getAutoConfigurationEntry()
方法,获取需要自动装配的 bean
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 1.判断自动装配开关是否打开
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
// 2.获取所有需要自动装配的bean
AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
getAutoConfigurationEntry()
getAutoConfigurationEntry()
方法的核心作用是在 Spring 启动时,对当前应用中的自动配置类进行去重和筛选。
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
// 1.判断自动装配开关是否打开
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
// 2.获取@EnableAutoConfiguration注解中的exclude和excludeName属性
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
// *3.获取位于META-INF/spring.factories下候选的所有配置类
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
// 4.1.移除重复的类
configurations = this.removeDuplicates(configurations);
// 4.2.排除exclude和excludeName数组中包含的配置类
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
// 4.3.根据当前应用环境进一步筛选配置类
configurations = this.getConfigurationClassFilter().filter(configurations);
// 5.触发自动配置事件
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
}
getCandidateConfigurations()核心方法
跟进到 getCandidateConfigurations()
方法,主要通过 SpringFactoriesLoader
和 ImportCandidates
来加载自动配置的候选类。
- 使用 SpringFactoriesLoader 加载
META-INF/spring.factories
文件中定义的与EnableAutoConfiguration.class
相关的实现类。 - 通过
ImportCandidates
加载与AutoConfiguration.class
相关的其他候选实现类,并将这些类添加到configurations
列表中。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 1.调用SpringFactoriesLoader获取工厂的实现类
List<String> configurations = new ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()));
// 2.使用ImportCandidates加载工厂的实现类
ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations, "xxx.");
return configurations;
}
最后,使用 Assert.notEmpty()
方法确保 configurations
列表中至少有一个候选类后,返回候选类。
条件化装配
常见注解
-
Bean 条件注解:
-
@ConditionalOnBean
:当容器里有指定 Bean 的条件下 -
@ConditionalOnMissingBean
:当容器里没有指定 Bean 的情况下 -
@ConditionalOnSingleCandidate
:当指定 Bean 在容器中只有一个,或者虽然有多个但是指定首选 Bean
-
-
Class 条件注解:
-
@ConditionalOnClass
:当类路径下有指定类的条件下 -
@ConditionalOnMissingClass
:当类路径下没有指定类的条件下
-
-
属性条件注解:
@ConditionalOnProperty
:指定的属性是否有指定的值 -
Web 应用条件注解:
-
@ConditionalOnNotWebApplication
:当前项目不是 Web 项目的条件下 -
@ConditionalOnWebApplication
:当前项目是 Web 项 目的条件下
-
-
其他条件注解:
-
@ConditionalOnResource
:类路径是否有指定的值 -
@ConditionalOnExpression
:基于 SpEL 表达式作为判断条件 -
@ConditionalOnJava
:基于 Java 版本作为判断条件 -
@ConditionalOnJndi
:在 JNDI 存在的条件下差在指定的位置
-
SpringFactoriesLoader原理
SpringFactoriesLoader
方法会根据传入的工厂类和类加载器,从 META-INF/spring.factories
文件中加载 「指定类型对应的工厂类名称」。
loadFactoryNames()
loadFactoryNames
方法是一个高级 API,它通过获取入参中的全限定类名 factoryTypeName
,在内部调用 loadSpringFactories()
方法获取返回的 Map 集合,并根据 factoryTypeName
获取了 Map 中的实现类的 List 集合。
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
// 获取工厂类的全限定名
String factoryTypeName = factoryType.getName();
// 从 loadSpringFactories 返回的 Map 中获取指定类型工厂的实现类
return (List)loadSpringFactories(classLoaderToUse)
.getOrDefault(factoryTypeName, Collections.emptyList());
}
loadSpringFactories()
loadSpringFactories
方法是更加底层的方法,通过缓存机制和类加载器获取 spring.factories
文件中所有配置的工厂及其实现类,将这些信息封装为 Map
集合后返回给上游的 API。
缓存机制
方法会检查是否已经通过当前类加载器加载过 spring.factories
文件。如果缓存 (cache
) 中已经存在相应的工厂信息,直接返回缓存的 Map<String, List<String>>
,避免重复加载。
Map<String, List<String>> result = (Map)cache.get(classLoader);
if (result != null) {
return result;
}
加载 META-INF/spring.factories
方法会通过类加载器查找所有路径下名为 META-INF/spring.factories
的文件。由于每个 JAR 包都可能包含一个 META-INF/spring.factories
文件,方法会返回一个 Enumeration<URL>
对象,表示找到的所有相关资源文件。
Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");
解析spring.factories文件
通过迭代逐个读取每个找到的 spring.factories
文件。对于每个文件,使用 PropertiesLoaderUtils.loadProperties()
将文件内容解析为 Properties
对象。
每个 Properties
对象对应一个 spring.factories
文件的内容,其中 key 是工厂类型(例如 org.springframework.context.ApplicationContextInitializer
),value 是逗号分隔的工厂实现类列表。
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
将工厂类型和实现类存入Map
遍历 Properties
的 entrySet()
。对于每个 entry
,key
是工厂类型的全限定类名,value
是对应的工厂实现类名(逗号分隔)。
工厂类型名称通过 entry.getKey()
获取,并使用 String.trim()
去除可能的空白字符。工厂实现类则将逗号分隔的字符串转换为实现类的数组。
while(var6.hasNext()) {
Map.Entry<?, ?> entry = (Map.Entry)var6.next();
String factoryTypeName = ((String)entry.getKey()).trim();
String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
String[] var10 = factoryImplementationNames;
int var11 = factoryImplementationNames.length;
for(int var12 = 0; var12 < var11; ++var12) {
String factoryImplementationName = var10[var12];
((List)result.computeIfAbsent(factoryTypeName, (key) -> {
return new ArrayList();
})).add(factoryImplementationName.trim());
}
}
Spring Boot Starter机制
Spring Boot Starter 是一组方便的依赖描述符,旨在简化 Maven 和 Gradle 项目的依赖管理。每个 Starter
通常包含与某种功能或库相关的依赖,开发者只需引入相应的 Starter
,就可以自动获得这些依赖及其配置。
实现原理
依赖管理
Spring Boot Starter本质上是一个Maven或Gradle依赖,包含了一组已配置好的常用库。例如,spring-boot-starter-web
包含了Spring MVC、Jackson(用于JSON处理)、Tomcat(作为默认的嵌入式Web服务器)等依赖。
通过在项目中添加Starter依赖,开发者无需逐个添加这些库。Starter通常命名为 spring-boot-starter-xxx
,其中xxx
表示特定的功能模块。例如:
-
spring-boot-starter-data-jpa
:包含Spring Data JPA和Hibernate依赖。 -
spring-boot-starter-security
:包含Spring Security依赖。 -
spring-boot-starter-web
:用于开发Web应用,包含Spring MVC及相关依赖。
自动装配机制
Spring Boot在启动时会自动扫描类路径下的资源文件和META-INF/spring.factories
,从中找到定义的自动配置类。每个自动配置类根据一组条件(如类路径中是否存在特定类或是否有某些配置属性)来决定是否启用。
自动装配机制基于@EnableAutoConfiguration
注解,它通过读取所有路径下 spring.factories
文件中的配置,加载与项目相关的自动配置类。例如:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.autoconfigure.MyAutoConfiguration
加载配置文件
Spring Boot默认会从classpath
下加载application.properties
或application.yml
配置文件,开发者可以通过这些文件提供自定义的配置属性,来覆盖自动配置中的默认值。此外,Spring Boot还支持通过命令行参数、环境变量等方式注入配置,以便在不同环境中灵活调整应用配置。
自定义Starter
创建工程
创建一个名为 thread-spring-boot-starter
的工程,使用 Maven
添加所需的基本依赖。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.3</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
编写配置类
创建自动配置类,使用 @Configuration
注解,并结合条件注解(如 @ConditionalOnClass
)来定义自动装配逻辑。
@Configuration
public class ThreadPoolAutoConfiguration {
@Bean
public ThreadPoolExecutor threadPoolExecutor() {
return new ThreadPoolExecutor(10, 10, 100,
TimeUnit.MICROSECONDS, new ArrayBlockingQueue<>(100));
}
}
注册配置类
在src/main/resources/META-INF
目录下创建一个spring.factories
文件,并添加以下配置:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.example.config.ThreadPoolAutoConfiguration
添加Starter依赖
将其打包并安装到本地Maven仓库。
mvn clean install
一旦安装完成,其他项目就可以通过在其pom.xml
文件中添加以下依赖来使用这个Starter了:
<!-- 引入手写的starter-->
<dependency>
<groupId>org.example</groupId>
<artifactId>thread-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
使用Starter
在其他项目中,可以通过@Autowired
注解获取ThreadPoolExecutor
实例。
@SpringBootTest(classes = ProjectApplication.class)
public class StarterTest {
@Autowired
private ThreadPoolExecutor threadPoolExecutor;
@Test
public void threadPoolTest() {
System.out.println("核心线程数:" + threadPoolExecutor.getCorePoolSize());
System.out.println("最大线程数:" + threadPoolExecutor.getMaximumPoolSize());
}
}