前言
项目的开发要求是不断进化的,而随着时间以及技术的推移,在项目中除了基本的编程语言外,还需要进行大量的应用服务整合。例如,在项目中使用 MySQL 数据库进行持久化存储,同时会利用 Redis 作为缓存存储,以及使用 RocketMQ 实现异构系统整合服务等。 但在早先使用 Spring 开发的时候,如果想要进行某些服务的整合,常规的做法是引入对应服务的依赖,而后进行一些 XML 的配置和一些组件的定义。而如今在 SpringBoot 中往往各个组件都是以 starter 形式出现的,并且简化了很多配置吗,如 spring-boot-starter-data-redis、rocketmq-spring-boot-starter等。
一、SpringBoot Starter 机制
SpringBoot 中的 starter 是一种非常重要的机制,能够抛弃以前在 Spring 中的繁杂配置,将其统一集成进 starter,应用者只需要在 maven 中引入 starter 依赖,SpringBoot 就能自动扫描到要加载的信息并启动相应的默认配置。starter 让我们摆脱了各种依赖库的处理以及需要配置各种信息的困扰。SpringBoot 会自动通过 classpath 路径下的类发现需要的 Bean,并注册进 IOC 容器。SpringBoot 提供了针对日常企业应用研发各种场景的 spring-boot-starter 依赖模块。所有这些依赖模块都遵循着约定成俗的默认配置,并允许我们调整这些配置,即遵循“约定大于配置”的理念。
Spring Boot Starter本质上是一种依赖管理和自动配置机制。通过引入一个Starter依赖,可以自动引入并配置一系列相关组件,极大地简化开发流程。比如使用spring-boot-starter-web,可以自动引入Spring MVC、Tomcat服务器、JSON解析器等,无需手动逐个配置。
二、Starter的自动配置原理
Spring Boot 会扫描所有 classpath 下 JAR 包中的 META-INF/spring.factories 文件,并按照其中的配置进行自动加载。
2.1 扫描机制
- Spring Boot 使用 SpringFactoriesLoader 类来加载 spring.factories 文件中的配置。
- 它会扫描 所有 依赖(包括 libs/ 和 dependencies 目录中的 JAR 包),寻找 META-INF/spring.factories,然后合并所有的配置项。
2.2 合并机制
- 如果多个 JAR 包(如 spring-boot-starter-web、spring-boot-starter-data-jpa 等)都定义了 spring.factories,Spring Boot 会把 所有 JAR 包中的配置合并,然后执行加载。
示例:
-
spring-boot-autoconfigure.jar
的META-INF/spring.factories
可能包含:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
- 另一个 JAR 包
my-custom-starter.jar
可能包含:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.custom.MyCustomAutoConfiguration
- Spring Boot 会把这两部分合并,最终加载:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
com.example.custom.MyCustomAutoConfiguration
2.3 加载顺序
- Spring Boot 不保证 spring.factories 配置的执行顺序,如果多个 JAR 包中存在相同的 Key(比如 EnableAutoConfiguration),它们都会被加载,但执行顺序不可预测。
- 如果需要控制加载顺序,可以使用 @AutoConfigureBefore 和 @AutoConfigureAfter。
2.4 META-INF/spring.factories 文件的作用
META-INF/spring.factories 是 Spring Boot 的自动配置机制 之一,它的主要作用是:
- 启用 Spring Boot 的自动配置(AutoConfiguration)
- 自动注册 Spring 组件(如监听器、工厂类等)
- 扩展 Spring Boot 功能
2.5 spring.factories 作用范围
类型 | 作用 |
---|---|
EnableAutoConfiguration | 自动配置 Spring Boot 组件(最常见) |
ApplicationContextInitializer | Spring 上下文初始化时执行 |
ApplicationListener | 监听 Spring 事件 |
FailureAnalyzer | 处理异常分析,提供更友好的错误信息 |
EnvironmentPostProcessor | 修改 Spring Environment,在启动时修改配置 |
2.6 SpringFactoriesLoader 如何扫描?
Spring Boot 使用 SpringFactoriesLoader.loadFactoryNames(Class<?> factoryType, ClassLoader classLoader) 方法来扫描所有 META-INF/spring.factories:
public static List<String> loadFactoryNames(Class<?> factoryType, ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
- loadSpringFactories(classLoader) 方法会 遍历所有 JAR 包,加载 spring.factories。
- 所有 JAR 包的 spring.factories 文件的内容都会被合并并解析。
2.7 如何避免 spring.factories 冲突?
如果多个 JAR 包的 spring.factories 中包含 相同的 Key,但不同的实现(比如多个 JAR 里都有 EnableAutoConfiguration),可能会导致:
- 重复加载(多个类都被注册)
- 冲突(不同的类覆盖了同一个 Bean)
解决方案
- 1、使用 spring-boot-autoconfigure 提供的新方式
- Spring Boot 2.7+ 推荐使用 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
- 这样可以更好地控制哪些配置应该被加载,不会导致 spring.factories 的全局合并问题。
- 2、使用 @ConditionalOnMissingBean 避免重复加载
- 在自动配置类中使用 @ConditionalOnMissingBean,避免同一个 Bean 被多次注册:
@Configuration
@ConditionalOnClass(MyService.class)
public class MyAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MyService myService() {
return new MyService();
}
}
- 3、使用 @AutoConfigureBefore 或 @AutoConfigureAfter
- 控制自动配置类的加载顺序:
@Configuration
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
public class MyCustomAutoConfiguration {
// 自定义配置
}
结论
✅ Spring Boot 确实会扫描所有 JAR 包的 META-INF/spring.factories 文件,并合并所有配置项。
✅ 这使得 Starter 组件可以自动注册需要的 Bean 和配置,但也可能导致 Bean 冲突或重复加载的问题。
✅ Spring Boot 2.7+ 推荐使用 AutoConfiguration.imports 作为更可控的替代方案。
三、Starter的命名规范建议
官方并没有强制性的命名约束,但 Spring Boot 社区和官方 Starter 一般遵循一些最佳实践,推荐使用 统一的命名规范,以提高可读性和易用性。
1️⃣ 官方 Starter 的命名规范
Spring Boot 官方 Starter 的命名规则通常是:
spring-boot-starter-<技术名称>
典型官方 Starter 例子
Starter | 说明 |
---|---|
spring-boot-starter-web | Web 开发 Starter,包含 Spring MVC、Tomcat 等 |
spring-boot-starter-data-jpa | JPA 数据访问 Starter |
spring-boot-starter-security | Spring Security Starter |
spring-boot-starter-thymeleaf | Thymeleaf 模板引擎 Starter |
结论:
- 所有官方 Starter 以 spring-boot-starter- 开头,然后跟 具体的技术名称。
- 这让开发者一眼就能知道 它是官方的,并且它支持什么技术。
2️⃣ 自定义 Starter 的推荐命名规则
对于 自定义 Starter,社区推荐使用:
<公司/团队前缀>-starter-<功能名称>
或者:
<组织名称>-spring-boot-starter-<功能名称>
推荐命名方式
命名示例 | 适用场景 |
---|---|
mycompany-starter-dingtalk | 企业内部使用,用于钉钉异常通知 |
com-example-spring-boot-starter-logging | 社区 Starter,支持 Spring Boot 日志增强 |
custom-starter-redis | 通用 Starter,适用于 Redis 相关增强功能 |
awesome-spring-boot-starter-cache | 开源项目,提供缓存增强 |
推荐命名规则
✅ 避免以 spring-boot-starter- 开头
- 这样可以 避免和官方 Starter 混淆,让用户明确知道它是第三方 Starter。
- 推荐:mycompany-starter-xxx 或 xxx-spring-boot-starter
- 不推荐:spring-boot-starter-xxx ❌(可能导致误以为是官方的)
✅ 前缀代表团队或组织名称,例如:
- alibaba-starter-seata(阿里巴巴 Seata 事务管理)
- baidu-starter-paddlepaddle(百度 PaddlePaddle AI 框架)
- mycompany-starter-wechat(企业内部的微信消息推送)
✅ 后缀描述功能
- starter-dingtalk(钉钉通知)
- starter-mq(消息队列支持)
- starter-log-enhancer(日志增强)
3️⃣ 约束和注意事项
官方约束
Spring Boot 不会 强制要求 Starter 必须符合某种命名规则,但官方有一些约定:
- 1、官方 Starter 需要由 Spring 团队维护(放在 org.springframework.boot 下面)。
- 2、社区或第三方 Starter 不能以 spring-boot-starter- 开头,避免误导用户。
依赖命名
如果 Starter 需要提供 自动配置,通常需要提供:
- 核心依赖(starter 依赖)
<artifactId>mycompany-starter-dingtalk</artifactId>
- 自动配置依赖(autoconfigure 依赖)
<artifactId>mycompany-starter-dingtalk-autoconfigure</artifactId>
这样可以 拆分 Starter 的自动配置和核心功能,提高灵活性。
结论
✅ 官方 Starter 以 spring-boot-starter- 开头,后面跟具体技术,如 spring-boot-starter-web。
✅ 自定义 Starter 推荐 mycompany-starter-xxx 或 xxx-spring-boot-starter,避免与官方混淆。
✅ 组织前缀 可加 mycompany- 或 com-example-,以区分不同的公司或团队。
✅ 最好拆分 starter 和 starter-autoconfigure,增强模块化和可扩展性。
四、自定义starter
4.1 步骤
-
1、新建两个模块,命名规范: xxx-spring-boot-starter
- 1.1、xxx-spring-boot-autoconfigure:自动配置核心代码
- 1.2、xxx-spring-boot-starter:管理依赖
- 1.3、ps:如果不需要将自动配置代码和依赖项管理分离开来,则可以将它们组合到一个模块中。但 SpringBoot 官方建议将两个模块分开。
-
2、在 xxx-spring-boot-autoconfigure 项目中
- 2.1、引入 spring-boot-autoconfigure 的 maven 依赖
- 2.2、创建自定义的 XXXProperties 类: 这个类的属性根据需要是要出现在配置文件中的。
- 2.3、创建自定义的类,实现自定义的功能。
- 2.4、创建自定义的 XXXAutoConfiguration 类:这个类用于做自动配置时的一些逻辑,需将上方自定义类进行 Bean 对象创建,同时也要让 XXXProperties 类生效。
- 2.5、创建自定义的 spring.factories 文件:在 resources/META-INF 创建一个 spring.factories 文件和 spring-configuration-metadata.json,spring-configuration-metadata.json 文件是用于在填写配置文件时的智能提示,可要可不要,有的话提示起来更友好。spring.factories用于导入自动配置类,必须要有。
3、在 xxx-spring-boot-starter 项目中引入 xxx-spring-boot-autoconfigure 依赖,其他项目使用该 starter 时只需要依赖 xxx-spring-boot-starter 即可
4.2 示例工程
新建工程 jokerku-log-spring-boot-autoconfigure
命名为jokerku-log-spring-boot-autoconfigure,代码编写在这个工程中。
新建工程 jokerku-log-spring-boot-starter
命名为 jokerku-log-spring-boot-starter 工程为一个空工程,只依赖jokerku-log-spring-boot-autoconfigure
jokerku-log-spring-boot-autoconfigure 项目结构:
1.新增注解命令为Log
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
String desc() default "";
}
2.新增LogInterceptor拦截器
@Slf4j
public class LogInterceptor implements HandlerInterceptor {
private static final ThreadLocal<TraceInfo> THREAD_LOCAL = new InheritableThreadLocal();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Log log = handlerMethod.getMethodAnnotation(Log.class);
if (log != null) {
long start = System.currentTimeMillis();
TraceInfo traceInfo = new TraceInfo();
String uri = request.getRequestURI();
String method = handlerMethod.getMethod().getDeclaringClass() + "#" + handlerMethod.getMethod();
traceInfo.setStart(start);
traceInfo.setRequestMethod(method);
traceInfo.setRequestUri(uri);
String traceId = UUID.randomUUID().toString().replaceAll("-", "");
traceInfo.setTraceId(traceId);
MDC.put(TraceInfo.TRACE_ID, traceId);
THREAD_LOCAL.set(traceInfo);
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Log logAnnotation = handlerMethod.getMethodAnnotation(Log.class);
if (logAnnotation != null) {
long end = System.currentTimeMillis();
TraceInfo traceInfo = THREAD_LOCAL.get();
long start = traceInfo.getStart();
log.info("requestUri:{}, requestMethod:{}, 请求耗时:{} ms", traceInfo.getRequestUri(), traceInfo.getRequestMethod(), end - start);
THREAD_LOCAL.remove();
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
3.新增config,将 LogInterceptor 加入拦截器中
@Configuration
public class LogAutoConfigure implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor());
}
}
4.核心点:在resources下新建 META-INF 文件夹,然后创建spring.factories文件
springboot自动装配会读取该配置文件,会将 LogAutoConfigure 这个类自动装配,此时strart包就被装配成功
# springboot自动装配机制 会读取该配置 进行自动装配
org.springframework.boot.autoconfigure.EnableAutoConfiguration = com.jokerku.autoconfigure.LogAutoConfigure
spring.factories 书写规则
spring.factories 由 key = value 结构组成
- key为接口类,可以使用spring的接口,可以使用自定义的接口(自定义接口实现类必须加上 @component才能被加载)
- value为需要加载的实现类,不是必须实现interface
org.springframework.boot.autoconfigure.EnableAutoConfiguration = \
com.jokerku.autoconfigure.LogAutoConfigure
org.springframework.boot.SpringApplicationRunListener= \
com.jokerku.testListener
org.springframework.context.ApplicationContextInitializer= \
com.jokerku.testInitializer
#自定义接口
com.jokerku.service.TestService = com.jokerku.service.impl.TestServiceImpl
#如其一个接口有多个实现,如下配置:
org.springframework.boot.logging.LoggingSystemFactory=\
org.springframework.boot.logging.logback.LogbackLoggingSystem.Factory,\
org.springframework.boot.logging.log4j2.Log4J2LoggingSystem.Factory,\
org.springframework.boot.logging.java.JavaLoggingSystem.Factory
其他项目引入jokerku-log-spring-boot-starter依赖
<dependency>
<groupId>com.jokerku</groupId>
<artifactId>jokeku-log-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
接口添加该注解
自定义 starter 时会可能会用到的注解
- @Conditional:按照一定的条件进行判断,满足条件给容器注册bean
- @ConditionalOnMissingBean:给定的在bean不存在时,则实例化当前Bean
- @ConditionalOnProperty:配置文件中满足定义的属性则创建bean,否则不创建
- @ConditionalOnBean:给定的在bean存在时,则实例化当前Bean
- @ConditionalOnClass: 当给定的类名在类路径上存在,则实例化当前Bean
- @ConditionalOnMissingClass :当给定的类名在类路径上不存在,则实例化当前Bean
- @ConfigurationProperties:用来把 properties 配置文件转化为bean来使用
- @EnableConfigurationProperties:使 @ConfigurationProperties 注解生效,能够在 IOC 容器中获取到转化后的 Bean
五、SpringBoot Starter 原理
想要了解 SpringBoot 是如何加载 starter 的(也就是 SpringBoot 的自动装配原理),首先就要从启动类上的 @SpringBootApplication 注解说起。 SpringBoot 通过 SpringApplication.run(App.class, args) 方法启动项目,在启动类上有 @SpringBootApplication 注解,研究上面的原理首先看 @SpringBootApplication 内部的组成结构,如下图:
下面对 @SpringBootConfiguration 和 @EnableAutoConfiguration 进行详解。
5.1 @SpringBootConfiguration 注解
@SpringBootConfiguration 内部结构,如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {...}
@EnableAutoConfiguration 注解
@EnableAutoConfiguration 内部结构,如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {...}
重点看 @AutoConfigurationPackage 注解和 @Import(AutoConfigurationImportSelector.class) 注解。
@AutoConfigurationPackage 注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
@AutoConfigurationPackage 上包含 @Import(AutoConfigurationPackages.Registrar.class) 注解 看其@Import进来的类AutoConfigurationPackages.Registrar类:
/**
* {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
* configuration.
*/
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
这是一个内部类,继续关注内部的 registerBeanDefinitions 方法中调用的 AutoConfigurationPackages#register 方法
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
if (registry.containsBeanDefinition(BEAN)) {
BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
}
else {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(BasePackages.class);
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(BEAN, beanDefinition);
}
}
register 方法 apidoc 解释:以编程方式注册自动配置包名称,随后的调用将把给定的包名添加到已经注册的包名中。您可以使用此方法手动定义将用于给定BeanDefinitionRegistry的基本包。通常,建议您不要直接调用此方法,而是依赖默认约定,其中包名是从@EnableAutoConfiguration配置类中设置的。
register 方法会生成一个 BasePackages 类型的 BeanDefinition,最终会扫描 basePackages 目录及其子目录下的类,将其全部注入 Spring IOC容器中。 总结: @AutoConfigurationPackage 注解最终会扫描 packageNames 目录下的类并将其全部注入到 Spring IOC 容器中。packageNames 为当前启动类的根目录(当前的根目录为 com.jokerku)。
@Import(AutoConfigurationImportSelector.class) 注解
首先解释 @Import注解在 Spring 中的作用: @Import 通过快速导入的方式实现把实例加入spring的IOC容器中
-
@Import({TestA.class}):这样就会把 TestA 注入进 IOC 容器,生成一个名字为 “com.demo.testA” 的 bean
所以 AutoConfigurationImportSelector 最终也会被 Spring 加载注入进 IOC 容器,重点关注AutoConfigurationImportSelector 中的内部类 AutoConfigurationGroup。
AutoConfigurationGroup 的 process 方法会调用 getAutoConfigurationEntry 方法,getAutoConfigurationEntry 的作用是获取自动配置项。其底层会通过 getCandidateConfigurations 方法调用SpringFactoriesLoader.loadFactoryNames 去 META-INF/spring.factories 目录下加载 org.springframework.boot.autoconfigure.EnableAutoConfiguration 自动配置项。 最终META-INF/spring.factories 目录下的 自动配置项会被 Spring IOC 容器进行加载。
流程图
SPI机制
SpringBoot 为了更好地达到 OCP 原则(即“对扩展开放,对修改封闭”的原则)通过将自动配置项写在 META-INF/spring.factories 目录下的方式进行加载。而这种方式其实就是 SPI 机制。 在 Java 中 提供了原生的 SPI 机制,Java SPI(Service Provider Interface)实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。SPI是一种为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。所以SPI的核心思想就是解耦。JDK SPI 提供了 ServiceLoader 类,入口方法是 ServiceLoader.load() 方法,ServiceLoader 会默认加载 META-INF/services/ 下的文件。 各个框架中都有类似 SPI 机制的实现,如 MyBatis 中的 Plugin、Dubbo 中的 Dubbo-SPI。