Spring Boot自动装配原理解析及自定义Starter开发指南

在使用Springboot做项目的时候,你是否好奇,为什么项目里只要引入了那些starter为后缀命名的依赖,spring容器中就自动配置了这些依赖中的实现类,你只需要注入即可。比如RedisTemplate、RabbitTemplate等。你是否想自己也实现一个springboot可以自动配置的依赖。那么通过本篇文章,你就可以更加清晰的理解spring的自动装配机制,以及手动实现一个starter依赖。

一、SPI机制

1.1 什么是SPI机制

SPI,字面意思:服务提供者的接口

我的理解是,他是专门提供给服务提供者去开发的一个接口。SPI将接口的定义和实现分离开来,将两者解耦(放在不同的包)。从而提升程序的可扩展性(支持多种实现)。

举例

很多框架都用到了SPI机制,比如Spring框架、数据库驱动、日志接口等。

以日志接口举例:slf4j提供了spi接口,然后有多个实现spi的依赖包可供选择,比如原生的logback或者其他开发者提供的log4j2。⭐(这里可以记下,贴合实际,面试有用)

image-20251128112847871

模块 主要职责 是否SPI接口
slf4j-api 提供日志门面API + SPI机制 包含SPI接口定义
logback-classic 日志实现 + SPI服务提供者 SPI实现者
log4j-slf4j-impl 适配器 + SPI服务提供者 SPI实现者

对比API

API(Application Programming Interface)跟SPI最大的区别就是,接口的定义和实现是放在同一个包中的,不方便进行扩展。

1.2 Java SPI实战演示

1.2.1创建服务提供者的接口jar包

首先,随便创建一个java项目,结构如图所示

Logger是公共接口,而LoggerService是测试SPI接口的类,Main是测试类

image-20251124160225409

Logger

1
2
3
4
public interface Logger {
void info(String msg);
void debug(String msg);
}

LoggerService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class LoggerService {
Logger logger;
public LoggerService(){
ServiceLoader<Logger> loader = ServiceLoader.load(Logger.class);
Iterator<Logger> iterator = loader.iterator();
//获取第一个Logger的实现类
if(iterator.hasNext()){
logger = iterator.next();
}
}

public void info(String msg){
if(logger == null){
System.out.println("没有找到logger的实现类");
return;
}
logger.info(msg);
}
public void debug(String msg){
if(logger == null){
System.out.println("没有找到logger的实现类");
return;
}
logger.debug(msg);
}
}

Main

1
2
3
4
5
6
7
public class Main {
public static void main(String[] args) {
LoggerService service = new LoggerService();
service.info("ldy info");
service.debug("ldy debug");
}
}

运行main方法,我们会发现控制台会输出

1
2
没有找到logger的实现类
没有找到logger的实现类

这是因为目前serviceLoader没有找到Logger的实现类。

我们打个jar包,然后再创建一个项目来实现Logger接口。

1.2.2创建服务提供者的实现jar包

创建新项目service-provider,将interface的jar包放到resource目录中,并手动右键导入到库中,项目结构如图所示

image-20251124161428956

导入jar包成功之后,编写自己的Logger接口的实现类(任意包名都可以的)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class LdyLogger implements Logger {
@Override
public void info(String s) {
System.out.printf("ldy info %s%n", s);
}

@Override
public void debug(String s) {
System.out.printf("ldy debug %s%n", s);
}

public static void main(String[] args) {
LoggerService service = new LoggerService();
service.info("这是info日志");
service.debug("这是debug日志");
}
}

最后,在resource目录的META-INF/services目录下创建名为Logger接口全类名的文件。

文件里面写入我们自己的Logger实现类的全类名。

然后任意写个类,或者直接在实现类中写上main方法,启动并测试LoggerService的info和debug方法。

我的打印结果如下:

1
2
ldy info 这是info日志
ldy debug 这是debug日志

可以看到,LoggerService成功使用我们的自定义的Logger实现类的方法进行了打印。

至此,SPI实战就完成了。但是你一定好奇,为什么在META-INF/service目录下创建对应文件并写入实现类的全类名后,LoggerService的构造方法可以成功将我们自定义的LdyLogger对象赋值给logger成员变量。这其实跟ServiceLoader脱不开关系,他也是SPI机制实现的关键。

1.2.3原理探究

Java中的SPI机制采用懒加载的方式,其实就是在每次调用ServiceLoaderload()方法产生的不同接口的(同一个接口会复用已有的结果)ServiceLoader实例对象的iterator()方法的时候,会先去找到class相对目录下的META-INF/services文件夹下的文件,将这个文件夹下面的{接口全限定名}文件加载到内存中,找到相应接口的具体实现类,找到类之后,就可以通过反射去生成对应的对象,保存到一个list集合中,所以可以通过迭代或者遍历的方式拿到对应接口的实现实例对象。

看ServiceLoader的load()和iterator()方法上面的注释即可知道ServiceLoader采用的就是懒加载的方式。

注意:针对同一个接口class的多个ServiceLoader,除了第一个以外,之后他们的获取到的实现类的对象是复用的。

不是每次调用 iterator() 都重新查找

1
2
3
4
5
6
7
8
ServiceLoader<MyInterface> loader = ServiceLoader.load(MyInterface.class);

// 第一次调用 iterator() - 触发真正的加载过程
Iterator<MyInterface> it1 = loader.iterator();
// 此时会:查找配置 → 解析类名 → 类加载 → 反射实例化 → 缓存结果

// 后续调用 iterator() - 返回新的迭代器,但复用已加载的结果
Iterator<MyInterface> it2 = loader.iterator();

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
LoggerService service = new LoggerService();
Field logger =
service.getClass().getDeclaredField("logger");
logger.setAccessible(true);
Object o = logger.get(service);
LoggerService service2 = new LoggerService();
Field logger2 =
service2.getClass().getDeclaredField("logger");
logger2.setAccessible(true);
Object o2 = logger2.get(service);

System.out.println(o2 == o); //true
}

1.3 ServiceLoader源码解析

本人的知识点欠缺部分:java的模块化知识点。

load(Class<S> service)

ServiceLoader.load()方法用于获取ServiceLoader的实例

1
2
3
4
5
6
public static <S> ServiceLoader<S> load(Class<S> service) {
//获取线程上下文类加载器
ClassLoader cl = Thread.currentThread().getContextClassLoader();
//返回ServiceLoader实例对象,Reflection.getCallerClass()是获取实际的调用者,就是获取load方法被调用时所在的类,比如A中调用的B的method,那么获取到的就是A.class
return new ServiceLoader<>(Reflection.getCallerClass(), service, cl);
}

ServiceLoader(Class<?> caller, Class<S> svc, ClassLoader cl)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//初始化 ServiceLoader 实例,用于加载服务提供者
private ServiceLoader(Class<?> caller, Class<S> svc, ClassLoader cl) {
Objects.requireNonNull(svc);
//检查java虚拟机是否已完成初始化
if (VM.isBooted()) {
//验证调用者的访问权限
checkCaller(caller, svc);
if (cl == null) {
cl = ClassLoader.getSystemClassLoader();
}
} else {

// if we get here then it means that ServiceLoader is being used
// before the VM initialization has completed. At this point then
// only code in the java.base should be executing.
//虚拟机初始化完成前只能执行java.base模块中的代码
Module callerModule = caller.getModule();
Module base = Object.class.getModule();
Module svcModule = svc.getModule();
if (callerModule != base || svcModule != base) {
fail(svc, "not accessible to " + callerModule + " during VM init");
}

// restricted to boot loader during startup
//限制使用引导类加载器
cl = null;
}
//设置要加载的服务类型及名称
this.service = svc;
this.serviceName = svc.getName();
//初始化模块层引用为null。layer用于定位提供者的模块层;使用类加载器定位提供者时为null
this.layer = null;
//设置用于加载服务的类加载器
this.loader = cl;
//安装控制相关
this.acc = (System.getSecurityManager() != null)
? AccessController.getContext()
: null;
}

iterator()

iterator()用于返回一个懒惰的加载并实例化服务的可用提供者的迭代器

真正的加载并实例化位于

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public Iterator<S> iterator() {

// create lookup iterator if needed
//lookupIterator1是一个服务查找迭代器,用于发现和加载服务提供者,只在第一次调用iterator方法时初始化
if (lookupIterator1 == null) {
lookupIterator1 = newLookupIterator();
}
//返回一个重写后的迭代器
return new Iterator<S>() {

// 记录reloadCount值,用于检测并发修改,reloadCount是记录清除服务实现缓存并重新加载的次数
final int expectedReloadCount = ServiceLoader.this.reloadCount;

// index into the cached providers list
int index;

/**
* Throws ConcurrentModificationException if the list of cached
* providers has been cleared by reload.
*/
private void checkReloadCount() {
if (ServiceLoader.this.reloadCount != expectedReloadCount)
throw new ConcurrentModificationException();
}

@Override
public boolean hasNext() {
checkReloadCount();
if (index < instantiatedProviders.size())
return true;
return lookupIterator1.hasNext();
}

@Override
public S next() {
checkReloadCount();
S next;
//如果查询的索引值小于缓存集合的大小,则可以直接从缓存中获取
if (index < instantiatedProviders.size()) {
next = instantiatedProviders.get(index);
} else {
//调用服务提供者的迭代器获取服务实例
next = lookupIterator1.next().get();
//instantiatedProviders是一个ArrayList,负责缓存服务实例
instantiatedProviders.add(next);
}
index++;
return next;
}

};
}

newLookupIterator()

这里面利用了策略模式,根据layer是否为null,创建并返回不同策略的迭代器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//用于创建查找服务提供者的迭代器
private Iterator<Provider<S>> newLookupIterator() {
assert layer == null || loader == null;
//如果模块层不为null,则返回能够在模块层层次结构中进行服务发现的迭代器
if (layer != null) {
return new LayerLookupIterator<>();
} else {
//用于查找模块化服务的迭代器
Iterator<Provider<S>> first = new ModuleServicesLookupIterator<>();
//用于延迟查找类路径下的服务的迭代器,重点!!!
Iterator<Provider<S>> second = new LazyClassPathLookupIterator<>();
//通过匿名内部类方式,返回迭代器的实现类,优先从first中获取服务提供者,确保模块化服务优先于传统的类路径服务被发现和加载。
return new Iterator<Provider<S>>() {
@Override
public boolean hasNext() {
return (first.hasNext() || second.hasNext());
}
@Override
public Provider<S> next() {
if (first.hasNext()) {
return first.next();
} else if (second.hasNext()) {
return second.next();
} else {
throw new NoSuchElementException();
}
}
};
}
}

LazyClassPathLookupIterator

这是传统的通过类路径加载服务提供者的迭代器,我们的spi实战就是利用这个迭代器实现的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
private final class LazyClassPathLookupIterator<T>
implements Iterator<Provider<T>>
{
//定义类路径下服务器配置文件的标准路径前缀
static final String PREFIX = "META-INF/services/";
//存储服务提供者名称,避免重复加载
Set<String> providerNames = new HashSet<>(); // to avoid duplicates

Enumeration<URL> configs;
Iterator<String> pending;

Provider<T> nextProvider;
ServiceConfigurationError nextError;

LazyClassPathLookupIterator() { }

/**
* Parse a single line from the given configuration file, adding the
* name on the line to set of names if not already seen.
*/
private int parseLine(URL u, BufferedReader r, int lc, Set<String> names)
throws IOException
{
String ln = r.readLine();
if (ln == null) {
return -1;
}
int ci = ln.indexOf('#');
if (ci >= 0) ln = ln.substring(0, ci);
ln = ln.trim();
int n = ln.length();
if (n != 0) {
if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
fail(service, u, lc, "Illegal configuration-file syntax");
int cp = ln.codePointAt(0);
if (!Character.isJavaIdentifierStart(cp))
fail(service, u, lc, "Illegal provider-class name: " + ln);
int start = Character.charCount(cp);
for (int i = start; i < n; i += Character.charCount(cp)) {
cp = ln.codePointAt(i);
if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
fail(service, u, lc, "Illegal provider-class name: " + ln);
}
if (providerNames.add(ln)) {
names.add(ln);
}
}
return lc + 1;
}

/**
* Parse the content of the given URL as a provider-configuration file.
*/
private Iterator<String> parse(URL u) {
Set<String> names = new LinkedHashSet<>(); // preserve insertion order
try {
URLConnection uc = u.openConnection();
uc.setUseCaches(false);
try (InputStream in = uc.getInputStream();
BufferedReader r
= new BufferedReader(new InputStreamReader(in, UTF_8.INSTANCE)))
{
int lc = 1;
while ((lc = parseLine(u, r, lc, names)) >= 0);
}
} catch (IOException x) {
fail(service, "Error accessing configuration file", x);
}
return names.iterator();
}

/**
* Loads and returns the next provider class.
*/
private Class<?> nextProviderClass() {
//第一次调用时,才初始化configs
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
if (loader == null) {
//ClassLoader.getSystemResources()会去每个依赖中找到对应的配置文件。每个类加载器,他们都有自己管理的类路径,比如URLClassLoader的初始化方法就让你提供url路径
configs = ClassLoader.getSystemResources(fullName);
} else if (loader == ClassLoaders.platformClassLoader()) {
// The platform classloader doesn't have a class path,
// but the boot loader might.
if (BootLoader.hasClassPath()) {
configs = BootLoader.findResources(fullName);
} else {
configs = Collections.emptyEnumeration();
}
} else {
configs = loader.getResources(fullName);
}
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
//按需解析每一个配置文件(每个jar包中的),pending是一个存储了当前的配置文件里的所有SPI接口的实现的全类名的迭代器
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
//如果全部遍历完了,表示没有下一个服务实现类的实例,就返回null
return null;
}
//获取下一个配置文件的全类名迭代器
pending = parse(configs.nextElement());
}

String cn = pending.next();
try {
//根据全类名,加载类的实例
return Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service, "Provider " + cn + " not found");
return null;
}
}

@SuppressWarnings("unchecked")
private boolean hasNextService() {
while (nextProvider == null && nextError == null) {
try {
Class<?> clazz = nextProviderClass();
if (clazz == null)
return false;

if (clazz.getModule().isNamed()) {
// ignore class if in named module
continue;
}

if (service.isAssignableFrom(clazz)) {
Class<? extends S> type = (Class<? extends S>) clazz;
Constructor<? extends S> ctor
= (Constructor<? extends S>)getConstructor(clazz);
ProviderImpl<S> p = new ProviderImpl<S>(service, type, ctor, acc);
nextProvider = (ProviderImpl<T>) p;
} else {
fail(service, clazz.getName() + " not a subtype");
}
} catch (ServiceConfigurationError e) {
nextError = e;
}
}
return true;
}

private Provider<T> nextService() {
if (!hasNextService())
throw new NoSuchElementException();

Provider<T> provider = nextProvider;
if (provider != null) {
nextProvider = null;
return provider;
} else {
ServiceConfigurationError e = nextError;
assert e != null;
nextError = null;
throw e;
}
}

@SuppressWarnings("removal")
@Override
public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}

@SuppressWarnings("removal")
@Override
public Provider<T> next() {
if (acc == null) {
return nextService();
} else {
PrivilegedAction<Provider<T>> action = new PrivilegedAction<>() {
public Provider<T> run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
}

二、SpringBoot的自动装配机制

2.1 自动装配核心原理

SpringBoot 的自动装配(Auto-Configuration)是其“约定优于配置”理念的核心体现。它的目标是springboot启动时,根据项目中引入的依赖和配置,自动注册相应的 Bean 到 Spring 容器中,从而减少开发者的手动配置。

自动装配的核心流程如下:

  1. 启动类注解 @SpringBootApplication
    该注解是一个组合注解,包含了 @EnableAutoConfiguration,用于开启自动配置。
  2. @EnableAutoConfiguration 注解
    该注解通过 @Import 导入了 AutoConfigurationImportSelector 类,该类负责加载所有符合条件的自动配置类。
  3. spring.factories(2.x)或 AutoConfiguration.imports(3.x)
    这些文件中定义了所有可用的自动配置类,SpringBoot 在启动时会读取这些文件,并尝试加载其中定义的配置类。
  4. 条件注解(如 @ConditionalOnClass@ConditionalOnBean
    自动配置类中大量使用了条件注解,只有满足特定条件(如类路径下存在某个类、容器中已存在某个 Bean 等)时,才会执行该配置。

2.2 springboot2.x和3.x的自动配置文件的差异

版本 配置文件路径 文件格式
SpringBoot 2.x META-INF/spring.factories Properties 格式
SpringBoot 3.x META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文本格式(每行一个类)

示例:

  • **2.x 的 spring.factories**:

    1
    2
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.example.MyAutoConfiguration
  • **3.x 的 AutoConfiguration.imports**:

    1
    com.example.MyAutoConfiguration

2.3 与SPI机制的关系

SpringBoot 的自动装配机制本质上是 SPI 思想在 Spring 框架中的实现

  • SPI 机制:Java 通过 META-INF/services/ 目录下的文件来声明接口的实现类
  • SpringBoot 自动装配:通过 META-INF/spring.factories(2.x)或 META-INF/spring/AutoConfiguration.imports(3.x)来声明自动配置类(不需要实现接口)。

两者都是通过外部配置文件来解耦接口(或配置)与实现,实现插拔式的扩展机制。

2.4 核心源码解析

SpringBoot自动装配的原理主要依赖于他的@SpringApplication注解,该注解内部包含了@EnableAutoConfiguration注解,这个注解内部又包含了@Import注解,携带AutoConfigurationImportSelector.class这个参数。

1
@Import({AutoConfigurationImportSelector.class})

在spring启动时,扫描到启动类包含了@Import注解,于是就将这个自动配置选择器类注册到spring容器当中。

1
2
3
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered{}

public interface DeferredImportSelector extends ImportSelector {}

因为AutoConfigurationImportSelector实现了DeferredImportSelector接口,这个接口又实现了ImportSelector。所以在spring在这个选择器类注册到容器之后,就会调用他的selectImports方法,负责获取所有要自动配置的类的全类名。而这,正是自动装配的开始

源码:

selectImports

1
2
3
4
5
6
7
8
9
10
11
12
public String[] selectImports(AnnotationMetadata annotationMetadata) {
//检查是否启用自动配置
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
//启用,则获取自动配置的条目
AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
//将自动配置类的列表转换为字符串数组(类的全限定名数组)
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}

isEnabled

1
2
3
4
5
6
//检查是否启用自动配置
protected boolean isEnabled(AnnotationMetadata metadata) {
//如果当前类是AutoConfigurationImportSelector的子类,则直接返回true,否则是原始类
//读取环境变量中的 spring.boot.enableautoconfiguration 属性,默认为true
return this.getClass() == AutoConfigurationImportSelector.class ? (Boolean)this.getEnvironment().getProperty("spring.boot.enableautoconfiguration", Boolean.class, true) : true;
}

getAutoConfigurationEntry

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//获取自动配置类的条目
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
//再次调用 isEnabled 方法确认自动配置是否启用
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
//获取注解属性
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
//加载候选配置,getCandidateConfigurations从spring.factories(2.x) 或者 EnableAutoConfiguration全类名.imports(3.x)文件加载候选自动配置类
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
//去重处理,移除重复的配置类
configurations = this.<String>removeDuplicates(configurations);
//根据注解属性,获取要排除的配置类
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
//检查排除类的有效性,如果排除类存在,但是不在候选配置列表,则抛出异常
this.checkExcludedClasses(configurations, exclusions);
//从候选配置列表去除要排除的类
configurations.removeAll(exclusions);
//根据配置类上面的条件注解如@ConditionalOnClass注解进行过滤
configurations = this.getConfigurationClassFilter().filter(configurations);
//发布自动配置导入事件
this.fireAutoConfigurationImportEvents(configurations, exclusions);
//返回包含配置类和排除类的AutoConfigurationEntry对象
return new AutoConfigurationEntry(configurations, exclusions);
}
}

fireAutoConfigurationImportEvents

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//发布自动配置导入事件
private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) {
//获取所有自动配置的监听器
List<AutoConfigurationImportListener> listeners = this.getAutoConfigurationImportListeners();
if (!listeners.isEmpty()) {
//创建事件对象,包含当前实例、配置类列表、排除类列表
AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);
//遍历
for(AutoConfigurationImportListener listener : listeners) {
//调用invokeAwareMethods方法注入相关依赖,如当前实例的ClassLoader、BeanFactory、Environment、ResourceLoader。因为监听器可能需要访问这些组件来完成他们的工作
this.invokeAwareMethods(listener);
//调用监听器的方法处理事件,如日志记录、监控等
listener.onAutoConfigurationImportEvent(event);
}
}

}

invokeAwareMethods

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//为实现Aware接口的类注入相关依赖
//好像也理解了为什么我之前测试实现aware接口,结果打印了无数的相同信息的原因,应该是都被每个实现Aware接口的类都相当于一个监听器,监听后续的所有类
//实现 BeanClassLoaderAware 接口 → 可以获得 ClassLoader
//实现 BeanFactoryAware 接口 → 可以获得 BeanFactory
//实现 EnvironmentAware 接口 → 可以获得 Environment
//实现 ResourceLoaderAware 接口 → 可以获得 ResourceLoader
private void invokeAwareMethods(Object instance) {
if (instance instanceof Aware) {
if (instance instanceof BeanClassLoaderAware) {
BeanClassLoaderAware beanClassLoaderAwareInstance = (BeanClassLoaderAware)instance;
beanClassLoaderAwareInstance.setBeanClassLoader(this.beanClassLoader);
}

if (instance instanceof BeanFactoryAware) {
BeanFactoryAware beanFactoryAwareInstance = (BeanFactoryAware)instance;
beanFactoryAwareInstance.setBeanFactory(this.beanFactory);
}

if (instance instanceof EnvironmentAware) {
EnvironmentAware environmentAwareInstance = (EnvironmentAware)instance;
environmentAwareInstance.setEnvironment(this.environment);
}

if (instance instanceof ResourceLoaderAware) {
ResourceLoaderAware resourceLoaderAwareInstance = (ResourceLoaderAware)instance;
resourceLoaderAwareInstance.setResourceLoader(this.resourceLoader);
}
}

}

在获取到所有自动配置的类的集合之后,spring容器内部会按序将它们注册到IOC容器中。

强烈建议,自己手动去看源码,收获蛮多的!!!

三、自定义Starter开发实战

3.1 创建jar包

首先创建一个maven的空项目,然后编写要注入到spring容器中的类,这里我使用的是一个接口和对应的实现类,当然也可以不需要接口。

1
2
3
4
5
6
7
8
9
10
11
public interface LdyService {
public void handle();
}


public class LdyServiceImpl implements LdyService {
@Override
public void handle() {
System.out.println("我是"+this.getClass().getName());
}
}

然后,如果你要使用的springboot版本是2.x,则在resources目录下创建**/META-INF/spring.facotries**文件,写入如下内容(自动配置类注解的全类名下写上要自动配置的类的全类名)

1
2
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ldy.config.LdyAutoConfiguration

如果springboot版本是3.x,则在resources目录下创建**/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports**文件,直接写入要自动配置的类的全类名

1
com.ldy.service.impl.LdyServiceImpl

最后,使用maven插件进行打包,即clean+package。将target文件夹下生成的jar包,放入到springboot项目使用的maven仓库文件夹中,项目目录的创建依据jar包的pom.xml文件的配置进行创建

image-20251121151954007

例如,根据上图,我需要在maven仓库中创建com\ldy\spring-boot-starter-ldy\1.0.0这四级目录,将jar包放入到该目录中即可

image-20251121152143636

3.2 使用jar包

随便(注意,maven仓库要一致)打开一个springboot工程项目,然后配置依赖并刷新

1
2
3
4
5
6
<!-- 自定义starter依赖 -->
<dependency>
<groupId>com.ldy</groupId>
<artifactId>spring-boot-starter-ldy</artifactId>
<version>1.0.0</version>
</dependency>

在一个Controller中注入并使用我们的这个自定义类

1
2
3
4
5
6
7
8
9
10
11
@RestController
@RequestMapping("/learn")
public class LearnController {
@Autowired
LdyService ldyService;
@GetMapping()
public String get(){
ldyService.handle();
return "ok";
}
}

然后,访问对应接口(例如:localhost:8080/learn),即可发现,成功的执行handle方法并在控制台打印对应信息

1
我是com.ldy.service.impl.LdyServiceImpl

以上,最简单的springboot启动器依赖就完成了,怎么样,是不是非常简单?😊

但是,如果我们想要引入其他依赖来完成这个starter,比如我们希望在starter里编写一个Controller,然后springboot项目启动就可以访问这个Controller接口(类似插件化实现),又该怎么做呢?做的时候会不会出现例如接口冲突、依赖冲突等问题呢?

让我们继续看下去

3.3 功能增强

要在starter里开发Controller接口,我们必然要引入spring相关的依赖包来使用对应的注解。

如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependencies>
<!-- 添加 Spring Context 依赖以使用 @Configuration、@Controller 等注解 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.1.14</version>
</dependency>

<!-- 添加SpringWeb依赖,使用Web注解,如@GetMapping等-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>6.1.14</version>
</dependency>

</dependencies>

然后创建一个Controller类并配置

1
2
3
4
5
6
7
8
@RestController
@RequestMapping("/ldy")
public class LdyController {
@GetMapping("/get")
public String get(){
return "这里是Ldy的Controller的/get路径的get方法";
}
}

将该Controller的全类名根据springboot项目的版本,配置到对应的配置文件中

image-20251121153840593

重新打包并更换jar包,然后重新启动springboot应用,发现可以成功访问localhost:8080/ldy/get接口

image-20251121153949819

最后,解答一些自己考虑过的问题:

1.spring-context、spring-webmvc等依赖的版本要和springboot项目的依赖版本一致吗?

答:不需要,但是需要特别注意版本差异。引入自定义starter之后,Maven根据就近定义原则,会选择最近的依赖版本,如果主项目已经声明了依赖,则会优先使用主项目的版本。如果starter的依赖版本和主项目的依赖版本差不多,或者使用到的api之间没什么变化(比如包名相同,使用的方法都存在),那么运行时是不会出错的。

这也解释了为什么很多依赖包都会有springboot的版本或者jdk版本的要求。像springaiAlibaba必须要求springboot版本是3.x,就是因为版本不兼容,很多使用到的api跟2.x版本不一样。