SpringBoot —— 关于 Starter 和 自动装配
- 前言
- 自制一个 Starter
- auto-configure 模块
- starter 模块
- app 模块
- 自动装配
- AutoConfigurationImportSelector
- 总结
前言
Spring Boot 的快速发展与流行,很大程度上依赖于 Starter 的出现。Starter 方便了 Spring 各项依赖的集成,通过 Starter 可以在 Spring Boot 中获取到所需相关技术的一站式支持(依赖、相关的自动配置文件和相关的 Bean),而无需通过示例代码和复制粘贴来获取依赖
例如,需要 Spring Web 支持时,可以通过引入 spring-boot-starter-web
依赖,它将自动为项目配置一个内嵌的 Tomcat 以及开启 Spring WebMvc 的功能
常用的 Starter:
-
spring-boot-starter
:核心 Starter,包括自动配置的支持、日志以及 YAML 解析等 -
spring-boot-starter-aop
:提供 Spring AOP 和 AspectJ 的面向切面编程支持 -
spring-boot-starter-jdbc
:提供 JDBC 支持(由 Tomcat JDBC 连接池提供支持) -
spring-boot-starter-actuator
:Spring Boot 的 Actuator 支持,其提供了生产就绪功能,帮助开发者监控管理应用 - 等等
自制一个 Starter
Starter 主要由两部分组成
-
auto-configure
,该模块主要提供我们要导入功能的实现代码 -
starter
模块主要管理相关的依赖,比如auto-configure
依赖
实际上,这两个模块完全可以合并为一个模块,但是基于 Spring 职责分明 的理念,建议分开
auto-configure 模块
接下来我们开发一个简易的 auto-configure
- 常常利用
@ConfigurationProperties
@EnableConfigurationProperties
将当前模块的属性绑定到 Environment 上 - 常常利用各种
@Conditional
相关注解比如@ConditionalOnClass
@ConditionalOnMissingBean
等注解控制组件的注册条件 - 常常利用
@AutoConfigureBefore
@AutoConfigureAfter
等注解控制组件的依赖关系
示例如下:
MyTestProperties
/**
* 前缀:my.test
* 属性:name
*/
@ConfigurationProperties(prefix = "my.test")
public class MyTestProperties {
private String name = "default";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
MyAutoConfiguration
@Configuration
// 类路径存在 MyCommandLinerRunner.class
@ConditionalOnClass(MyCommandLinerRunner.class)
// 当前容器不存在 CommandLineRunner 类型的 bean
@ConditionalOnMissingBean(CommandLineRunner.class)
// MyTestProperties 属性配置
@EnableConfigurationProperties(MyTestProperties.class)
public class MyAutoConfiguration {
/**
* 注册一个 MyCommandLinerRunner
* @return
*/
@Bean
public CommandLineRunner myCommandLineRunner() {
return new MyCommandLinerRunner();
}
}
MyCommandLinerRunner
/**
* 输出配置的 MyTestProperties.name 属性
*/
public class MyCommandLinerRunner implements CommandLineRunner {
@Autowired
MyTestProperties myTestProperties;
@Override
public void run(String... args) throws Exception {
System.out.println("&&&&&&&&&");
System.out.println(myTestProperties.getName());
System.out.println("&&&&&&&&&");
}
}
最后,作为一个自定义的 moudle
,Spring 自然无法加载这个配置类,因此我们借用 Spring Boot 的 自动装配 机制,在 src\main\resources\META-INF\spring.factories
文件中添加自动配置信息:org.springframework.boot.autoconfigure.EnableAutoConfiguration=my.autoconfigure.config.MyAutoConfiguration
则该类会在 Spring Boot 应用启动时被加载
starter 模块
该模块管理依赖即可,在此示例中我们仅需要引入 auto-configure
依赖即可
pom.xml
<dependencies>
<dependency>
<groupId>com.xsn</groupId>
<artifactId>my-spring-boot-autoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
app 模块
此处的 app
模块即代表我们使用 auto-configure
功能的模块,此处直接引入 starter
模块依赖,然后启动 Spring Boot 应用即可,同时我们还可以在 yaml
文件里自定义 auto-configure
模块的配置属性
pom.xml
<dependencies>
<dependency>
<groupId>com.xsn</groupId>
<artifactId>my-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
application.yml
my:
test:
name: dd
AppApplication
@SpringBootApplication
public class AppApplication {
public static void main(String[] args) {
SpringApplication.run(AppApplication.class, args);
}
}
启动模块,定义的属性值成功输出
&&&&&&&&&
dd
&&&&&&&&&
至此,我们实现了一个简易的 Starter,其中有一个步骤,我们将自定义的配置类名添加到 src\main\resources\META-INF\spring.factories
文件中,这便是依赖于 Spring Boot 提供的 自动装配 机制
自动装配
在我们的 Spring Boot 启动类上的 @SpringBootApplication
注解中,我们可以看到一个 @EnableAutoConfiguration
注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication
进入该注解,可以看到 @Import({AutoConfigurationImportSelector.class})
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration
该注解 @Import
可以搭配 普通配置类
ImportSelector
ImportBeanDefinitionRegistrar
在容器中注册对应的 BeanDefinition
关于 @Import,可以阅读下面文章
Spring —— 关于 @Configuration @Import ImportSelector ImportBeanDefinitionRegistrar
此处 Import 的 AutoConfigurationImportSelector 就是一个 ImportSelector,因此 ConfigurationClassPostProcessor 会在解析主配置类时,执行 AutoConfigurationImportSelector#selectImports
方法
AutoConfigurationImportSelector
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
/**
* 获取所有符合条件的 EnableAutoConfiguration 装配类
*/
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
------------- getAutoConfigurationEntry ------------
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
/**
* 获取所有备选装配类 (EnableAutoConfiguration)
*/
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 去重
configurations = removeDuplicates(configurations);
/**
* 从 exclude excludeName 和 Environment 的 spring.autoconfigure.exclude 属性
* 中解析需要排除的类
*/
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
// AutoConfigurationImportFilter 过滤
configurations = getConfigurationClassFilter().filter(configurations);
/**
* 获取自动装配的 AutoConfigurationImportListener 并发布
* AutoConfigurationImportEvent
*/
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
所以,当我们将 auto-configure
的配置类名配置到对应的 spring.factories
文件中后,Spring Boot 并加载了该配置类
总结
本文从一个简单的 Starter Demo 入手,简单了解了 Starter 的使用机制,并同时对 自动装配 做了初步了解,这也是 Spring Boot 及其重要的机制