示例:hello-spring-boot-starter
为了理解SpringBoot Starter的写法,以hello-spring-boot-starter作为示例来讲解
创建父模块hello-spring-boot-starter-project
hello-spring-boot-starter-project将作为整个项目的父模块,其pom.xml文件如下:
<!--添加子模块-->
<modules>
<module>hello-spring-boot-starter</module>
<module>hello-spring-boot-starter-autoconfigure</module>
</modules>
<!--定义的参数-->
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-boot.version>3.0.7</spring-boot.version>
</properties>
<!--依赖的配置约定-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>3.5.11</version>
</dependency>
</dependencies>
</dependencyManagement>
创建子模块hello-spring-boot-starter-autoconfigure
hello-spring-boot-starter-autoconfigure作为父模块hello-spring-boot-starter-project的一个子模块,其pom.xml关键配置如下:
<!--设置父模块-->
<parent>
<groupId>edu.whut</groupId>
<artifactId>hello-spring-boot-starter-project</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
</dependencies>
在其模块内部创建三个文件
HelloService.java:
public class HelloService {
private final HelloProperties properties;
public HelloService(HelloProperties properties) {
this.properties = properties;
}
public void sayHello() {
System.out.println("Hello,"+properties.getObject());
}
}
HelloProperties.java:
@ConfigurationProperties(prefix = "hello")
public class HelloProperties {
private String object;
public String getObject() {
return object;
}
public void setObject(String object) {
this.object = object;
}
}
HelloAutoConfiguration.java:
//标识自动配置类
@AutoConfiguration
@EnableConfigurationProperties(HelloProperties.class)
public class HelloAutoConfiguration {
@Bean
//在application文件中配置了hello.object配置才会构造这个bean
@ConditionalOnProperty(prefix = "hello", name = "object")
public HelloService helloService(HelloProperties properties) {
return new HelloService(properties);
}
}
在hello-spring-boot-starter-autoconfigure模块中的resources下的META-INF下的spring目录下创建一个名为org.springframework.boot.autoconfigure.AutoConfiguration.imports的文件,文件中写入:
edu.whut.HelloAutoConfiguration
创建hello-spring-boot-starter子模块
hello-spring-boot-starter作为父模块hello-spring-boot-starter-project的一个子模块,其pom.xml关键配置如下:
<!--指定父模块-->
<parent>
<groupId>edu.whut</groupId>
<artifactId>hello-spring-boot-starter-project</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>edu.whut</groupId>
<artifactId>hello-spring-boot-starter-autoconfigure</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
这样这个模块就完成了
使用hello-spring-boot-starter
随意创建一个springboot项目,在main方法中查找对应的名为helloService的bean
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(DemoApplication.class, args);
Object bean = applicationContext.getBean("helloService");
System.out.println(bean);
}
结果为:
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'helloService' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:978)
at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1381)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1296)
at com.example.demo.DemoApplication.main(DemoApplication.java:12)
这是正常的,因为现在还未设置配置,更改aplication.properties,写入:
hello.object="world"
再次运行程序,现在就可以获取到starter中配置的bean:
edu.whut.HelloService@4463d9d3
为什么要使用springboot starter?
有人可能好奇,这么大费周章,就是为了把一个对象注入到IOC容器中?那为什么不直接在项目里注入?
因为每个Starter是一个高内聚的功能模块,通过依赖传递和条件化配置(@ConditionalOnClass等)实现“智能装配”,避免冗余代码。
为什么不直接写starter,反而需要一个autoconfigure?
这是为了遵循Spring Boot官方提倡的“关注点分离”原则,将自动配置逻辑和依赖管理解耦,让架构更清晰、更易维护。
autoconfigure包含条件化配置类、@ConfigurationProperties、META-INF/spring.factories(或org.springframework.boot.autoconfigure.AutoConfiguration.imports)
starter仅一个pom.xml,聚合autoconfigure模块 + 该功能所需的所有第三方依赖
springboot starter自动装配的原理
@SpringBootApplciation注解是一个组合注解,这个注解被一个@EnableAutoConfiguration所注解。
@EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class) // 核心关键
public @interface EnableAutoConfiguration {
// ...
}
ImportSelector是一个用于动态、编程式地选择要导入的配置类的核心接口,它是一个函数式接口,核心方法是:
String[] selectImports(AnnotationMetadata importingClassMetadata);
这个方法根据给定的注解元数据(被@Import注解的类的信息),返回一个由全限定类名组成的字符串数组。这些返回的类名会在运行时被Spring容器处理,就像它们原本就被@Import注解直接引用一样,其内部的@Configuration、@Bean等注解会被正常解析。
AutoConfigurationImportSelector
AutoConfigurationImportSelector实现了ImportSelector接口,实现了selectImports方法。
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 1. 检查是否开启了自动装配(默认是开启的)
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
因此核心的逻辑是getAutoConfigurationEntry(annotationMetadata)
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
//加载所有候选配置类
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes); //移除重复和显式排除的类
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
//进行条件注解的筛选
configurations = this.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
}
在这个方法中,核心逻辑只有三步:
- 加载所有的候选配置类
- 移除重复和显式排除的类
- 进行“条件注解”筛选
加载候选配置类
调用 getCandidateConfigurations()。这个方法会去约定好的位置,读取一个文件,这个文件里列出了所有可能被加载的自动配置类。
这个约定好的位置,在springboot3是META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,这个文件的每一行是一个全类名,在springboot2.x中是META-INF/spring.factories,org.springframework.boot.autoconfigure.EnableAutoConfiguration为键的值就是一系列全类名。
无论哪种格式,此时得到的都是一个巨大的List,包含了Spring Boot所有内置的(如DataSourceAutoConfiguration, WebMvcAutoConfiguration)以及第三方starter提供的自动配置类。因此要进行后续的排除和筛选。
总结
- 启动:执行
SpringApplication.run(),启动Spring容器 - 触发:容器解析主类上的
@SpringBootApplication->@EnableAutoConfiguration - 决策:
@EnableAutoConfiguration导入AutoConfigurationImportSelector - 扫描:
AutoConfigurationImportSelector从META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(Spring Boot 3)中读取所有候选自动配置类。 - 排除和筛选:对候选列表进行层层筛选。
- 导入:将最终满足所有条件的自动配置类的全限定名数组返回给容器。
- 解析与注册:容器将这些自动配置类当作普通的
@Configuration类进行解析,将其内部符合条件的@Bean方法注册为Bean定义。