为了理解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定义。
到此这篇关于springboot starter的用法以及原理小结的文章就介绍到这了,更多相关springboot starter用法内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论