大家好,这里是 盛码笔记。
本篇我们来聊一聊 Spring Boot 的“魔法”是如何实现的。你可能已经用过 Spring Boot
快速搭建项目,但有没有想过:为什么只需要引入几个 starter
,Spring Boot
就能自动配置好整个应用环境?
这背后的核心机制就是:起步依赖(Starter Dependencies
) 和 自动装配(Auto-Configuration
)。本文将从源码角度带你深入理解这两个机制的底层原理,建议你具备一定的 Spring 基础后再阅读。
(一)起步依赖
依赖传递机制(Maven/Gradle)
依赖传递的话,在讲maven的时候有提到过,下面是地址(这里就不展开了)
Maven详细教程(包含安装)-CSDN博客
-
核心作用:Starter 本身是一个空项目,仅包含一个
pom.xml
(或 Gradle 构建文件),通过 Maven 的依赖传递聚合了某个场景所需的全部依赖。 -
示例:
spring-boot-starter-web
的pom.xml
中声明了:<dependencies><dependency>spring-boot-starter</dependency><dependency>spring-boot-starter-json</dependency><dependency>spring-webmvc</dependency><dependency>spring-boot-starter-tomcat</dependency> </dependencies>
-
效果:用户只需引入
spring-boot-starter-web
,即可自动获得 Web 开发所需的所有库(Tomcat、Jackson、Spring MVC 等)。
(二)自动配置
- 原理: Spring Boot 在启动时扫描 classpath,根据存在的类、Bean 定义以及属性设置,自动配置 Spring 应用所需的各种组件(如 DataSource, JPA, MVC, Security 等)。
- 实现:
@EnableAutoConfiguration
注解(通常由@SpringBootApplication
包含)启用自动配置。- 大量的
XxxAutoConfiguration
类(在spring-boot-autoconfigure
jar 中),这些类使用@Configuration
定义配置,并使用@ConditionalOnXxx
(条件注解)来决定配置是否生效(例如@ConditionalOnClass
,@ConditionalOnMissingBean
,@ConditionalOnProperty
)。
Bean的导入机制
1. @ComponentScan
作用:
自动扫描指定包及其子包下所有被 @Component
、@Service
、@Controller
、@Repository
等注解标记的类,并注册为Bean。
特点:
- 基于包路径扫描:通过
basePackages
或basePackageClasses
指定扫描范围。 - 批量注册:一次性注册多个Bean。
- 依赖组件注解:目标类必须被Spring的组件注解标记(如
@Component
)。 - 过滤规则:支持通过
includeFilters
/excludeFilters
自定义扫描规则。
@Configuration
@ComponentScan(basePackages = "com.example.service",includeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*Service")
)
public class AppConfig {}
适用场景:
项目中自定义组件的批量注册(如Service、Controller层)。
2. @Import
作用:
显式导入一个或多个配置类(@Configuration
)、普通类(视为Bean)或 ImportSelector
/ImportBeanDefinitionRegistrar
的实现类。
特点:
- 精确导入:直接指定要导入的类(而非包扫描)。
- 无需组件注解:被导入的类不需要被
@Component
标记。 - 支持多种类型:
- 配置类:导入其他
@Configuration
类。 - 普通类:直接作为Bean注册。
ImportSelector
:动态选择导入类。ImportBeanDefinitionRegistrar
:编程式注册Bean。
- 配置类:导入其他
@Configuration
@Import({ DataSourceConfig.class, // 导入配置类MyUtility.class, // 普通类(注册为Bean)MyImportSelector.class // 动态选择导入
})
public class AppConfig {}
适用场景:
- 导入第三方库的配置类(如
DataSourceConfig
)。 - 注册未被组件注解标记的类(如工具类)。
3. ImportSelector
接口
作用:
通过编程方式动态选择要导入的配置类或Bean类,返回类的全限定名字符串数组。
特点:
- 动态决策:根据运行时条件(如配置参数、环境变量)决定导入哪些类。
- 解耦设计:将导入逻辑从
@Import
注解中分离。 - 无侵入性:目标类无需实现任何接口或注解。
实现步骤:
- 实现
ImportSelector
接口。 - 重写
selectImports()
方法,返回要导入的类的全限定名。 - 通过
@Import
引入该ImportSelector
。
public class MyImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {// 根据当前的运行环境动态选择配置文件(dev和prod环境)if (System.getenv("env").equals("prod")) {return new String[]{"com.example.ProdDataSourceConfig"};}else {return new String[] { "com.example.DevDataSourceConfig" };}}
}// 通过@Import引入
@Configuration
@Import(MyImportSelector.class)
public class AppConfig {}
适用场景:
需要根据条件动态注册Bean(如环境区分、特性开关)。
4. @Bean
方法
在 @Configuration
类中使用 @Bean
注解的方法:
@Configuration
public class AppConfig {@Beanpublic DataSource dataSource() {return new HikariDataSource();}
}
特点:
- 适用于实例化过程复杂的 Bean(如连接池)
- 可显式控制 Bean 的初始化逻辑
5. ImportBeanDefinitionRegistrar
(了解)
编程式注册 BeanDefinition:
public class CustomRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {registry.registerBeanDefinition("customBean", new RootBeanDefinition(CustomBean.class));}
}
特点:
- 最底层的 Bean 注册方式
- 可操作 BeanDefinition 元数据(作用域/Lazy/初始化方法等)
源码剖析
springboot版本为3.4.7
-
通过
@SpringBootApplication
是入口,点击进去。通过下面的源码可以发现,知道了为什么
@SpringBootApplication
扫描、注册当前包及子包下的Bean了,还可以当配置类使用。// 注解使用范围(类、接口或枚举类型) @Target({ElementType.TYPE}) // 保留策略:注解信息保留在运行时 @Retention(RetentionPolicy.RUNTIME) // 注解会被包含在 JavaDoc 中 @Documented // 继承:一个类使用了该注解,其子类将自动继承该注解 @Inherited// 这个注解相当于@Configuration的增强版(配置类) @SpringBootConfiguration // 自动配置的核心注解(启用 Spring Boot 的自动配置机制) @EnableAutoConfiguration// 启用 Spring 的组件扫描功能,并通过自定义过滤器排除某些类不被扫描到容器中 // 不指定包路径,默认扫描、注册当前包及子包下的Bean @ComponentScan(excludeFilters = {@Filter( // 排除过滤器type = FilterType.CUSTOM, // 表示使用开发者自定义的过滤器逻辑classes = {TypeExcludeFilter.class} // 指定具体的过滤器类 ), @Filter(type = FilterType.CUSTOM,classes = {AutoConfigurationExcludeFilter.class} // 排除那些已经被标记为“自动配置”的类 )} ) public @interface SpringBootApplication {// 这里省略了 }
再看一下
@SpringBootConfiguration
里面,下文可知,@SpringBootConfiguration
的功能相当于增强版的@Configuration
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented // 配置类注解的功能 @Configuration // 提升性能,支持编译时索引,加快组件扫描 @Indexed public @interface SpringBootConfiguration {@AliasFor(annotation = Configuration.class)boolean proxyBeanMethods() default true; }
-
再点击
@EnableAutoConfiguration
,进去继续看Spring Boot 自动装配的核心是
@EnableAutoConfiguration
→AutoConfigurationImportSelector
(实现ImportSelector
)@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited // 将主配置类(即标注了 @EnableAutoConfiguration 的类)所在的包作为自动扫描的根包。 @AutoConfigurationPackage @Import({AutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration {String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";Class<?>[] exclude() default {};String[] excludeName() default {}; }
然后是
@AutoConfigurationPackage
,它的作用是将主配置类所在的包作为自动扫描的根包。(至于后续逻辑就不展开了)在 Spring Boot 自动配置过程中,Spring 需要知道哪些包是“主应用包”,即:
- 包含主配置类的包;
- 需要被自动扫描和处理的包;
这些信息会被存储在一个特殊的 Bean 中,例如通过
AutoConfigurationPackages.register(...)
注册到容器中。其他自动配置类(如
DataSourceAutoConfiguration
)会依赖这些包路径来判断是否启用某些自动配置逻辑。@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited // 从使用 @AutoConfigurationPackage 注解的类中读取 basePackages 和 basePackageClasses 属性; // 将这些包路径注册到 Spring 容器中; // 供后续 Spring Boot 自动配置机制使用。 @Import({AutoConfigurationPackages.Registrar.class}) public @interface AutoConfigurationPackage {String[] basePackages() default {};Class<?>[] basePackageClasses() default {}; }
-
接下来需要点进入
AutoConfigurationImportSelector
类里面- 该类实现了
DeferredImportSelector
接口,而DeferredImportSelector
接口继承于ImportSelector
接口,所以实现了selectImports
方法。这里与上文第三种导入Bean的方法类似,需要一个字符串数组。 - 这里不清楚的就是
getAutoConfigurationEntry
,跳转过去
// 代码部分省略 /*** 根据注解元数据选择需要导入的自动配置类。** 该方法通常用于 Spring Boot 的自动配置机制中,根据当前类上的注解信息* 决定应该导入哪些自动配置类(即返回对应的全限定类名数组)。** @param annotationMetadata 注解元数据,描述了当前类上使用的注解信息* @return 返回一个字符串数组,包含所有需要导入的自动配置类的全限定名*/ public String[] selectImports(AnnotationMetadata annotationMetadata) {// 如果自动配置未启用,则直接返回空数组,不导入任何配置类if (!this.isEnabled(annotationMetadata)) {return NO_IMPORTS;} else {// 获取自动配置条目(包含要导入的配置类集合)AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);// 将配置类集合转换为字符串数组并返回return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());} }
点击
getAutoConfigurationEntry
方法跳转过去- 根据该方法的名称和返回值可以初步推测该方法时返回一个自动配置的对象
- 该方法围绕
configurations
这个字符串列表集合,需要知道它是什么就再点击getCandidateConfigurations
过去
/*** 获取自动配置条目(包含要导入的自动配置类和被排除的类)** 该方法主要完成以下工作:* 1. 检查是否启用自动配置* 2. 获取注解属性* 3. 加载所有候选的自动配置类* 4. 去重处理* 5. 处理排除类* 6. 过滤不符合条件的配置类* 7. 发布自动配置导入事件** @param annotationMetadata 注解元数据,描述当前类上的注解信息* @return 返回一个 AutoConfigurationEntry 对象,包含自动配置类和排除类*/ protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {// 1. 判断是否启用了自动配置,如果没有启用,则返回一个空的 AutoConfigurationEntryif (!this.isEnabled(annotationMetadata)) {return EMPTY_ENTRY;} else {// 获取注解属性(例如 @EnableAutoConfiguration 上的 exclude 或 excludeName 属性)AnnotationAttributes attributes = this.getAttributes(annotationMetadata);// 获取所有的候选自动配置类(从 META-INF/spring.factories 中读取)List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);//去除重复的配置类configurations = this.removeDuplicates(configurations);//获取需要排除的自动配置类(通过注解属性 exclude / excludeName)Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);this.checkExcludedClasses(configurations, exclusions);// 从候选配置类中移除被排除的类configurations.removeAll(exclusions);configurations = this.getConfigurationClassFilter().filter(configurations);this.fireAutoConfigurationImportEvents(configurations, exclusions);// 将处理后的配置类和排除类封装成 AutoConfigurationEntry 返回return new AutoConfigurationEntry(configurations, exclusions);} }
点击
getCandidateConfigurations
过去在这个方法里面可以知道这是从某个路径中加载配置类,并返回字符串集合。
/*** 加载所有候选的自动配置类*/ protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {// 从 META-INF/spring/...imports 文件中加载自动配置类ImportCandidates importCandidates = ImportCandidates.load(this.autoConfigurationAnnotation, this.getBeanClassLoader());// 获取配置类列表List<String> configurations = importCandidates.getCandidates();// 校验是否找到配置类,避免为空Assert.state(!CollectionUtils.isEmpty(configurations), "No auto configuration classes found in META-INF/spring/" + this.autoConfigurationAnnotation.getName() + ".imports. If you are using a custom packaging, make sure that file is correct.");return configurations; }
- 该类实现了
-
获取加载配置类的全路径(包名+类名)
采用debug断点调试
- 在
getCandidateConfigurations
方法里面打一个断点 - 然后运行主类即可
可以发现
this.autoConfigurationAnnotation.getName()
获取的值是org.springframework.boot.autoconfigure.AutoConfiguration
结合上面代码中长字符串的拼接加载配置类的完整路径是
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
- 在
-
查看配置类
怎么知道这个路径对不对呢?直接找出来看一下就知道了呗
项目左边打开外部库,根据这个路径找对应的文件
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
打开文件可以发现全都是全类名(包名+类名),再结合上面讲Bean导入机制时,实现ImportSelector
接口的配置类可以全类名字符串数组加载、注册Bean。 -
随便点击进入一个配置类,以
GsonAutoConfiguration
为例这里有代码创建了Gson的Bean实例,所以很多类不需要手动声明便可以直接使用,然后有一些以
Conditional
开头的注解作用是按条件加载对应的Bean,后面会详细讲解。
按条件装配
-
@ConditionalOnClass
-
作用: 当且仅当项目的类路径 (classpath) 上存在指定的类时,才会加载被注解标记的配置(配置类、
@Bean
方法、@Component
等)。 -
场景:
-
自动配置的核心: Spring Boot Starter 的自动配置类大量使用此注解。例如,
DataSourceAutoConfiguration
可能这样@Configuration @ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class }) public class DataSourceAutoConfiguration {// ... 只有在类路径上有 DataSource 和 EmbeddedDatabaseType 时才配置数据源 }
这确保了只有当你引入了数据库驱动(如
HikariCP
,tomcat-jdbc
)等依赖(它们会提供DataSource
实现类)时,Spring Boot 才会尝试自动配置数据源。避免了因为缺少依赖而抛出ClassNotFoundException
。 -
可选功能: 当你开发一个库或 Starter,并且某些功能需要特定的第三方库支持时,可以使用
@ConditionalOnClass
来确保只有用户引入了该库,你的功能才会被激活。
-
-
注意事项: 指定的类名必须是全限定名(
com.example.SomeClass
)。Spring Boot 通常使用name
属性指定字符串形式的类名(避免在编译时就需要该类存在),但也支持直接使用Class<?>[] value()
。
-
-
@ConditionalOnMissingClass
-
作用: 当且仅当项目的类路径 (classpath) 上不存在指定的类时,才会加载被注解标记的配置。
-
场景:
-
提供默认实现或回退方案: 当某个功能有多个实现(如日志框架:Logback, Log4j2),你可以为其中一个实现(如 Logback)配置默认 Bean,但条件设置为类路径上不存在另一个实现(如 Log4j2)的关键类。
@Configuration @ConditionalOnMissingClass("org.apache.logging.log4j.core.LoggerContext") // 假设 Log4j2 的核心类 public class DefaultLoggingConfiguration {@Beanpublic Logger myLogger() {// 配置默认的 Logback Logger} }
这样,如果用户引入了 Log4j2,这个默认的 Logback 配置就不会生效。
-
环境隔离: 在特定环境(如测试)下排除某些不需要的配置。
-
-
注意事项: 同样需要指定全限定名。使用相对较少,因为
@ConditionalOnClass
和@ConditionalOnBean
/@ConditionalOnMissingBean
组合通常能覆盖大部分需求。
-
-
@ConditionalOnBean
-
作用: 当且仅当 Spring 应用上下文 (IoC 容器) 中已经存在(已经定义或即将定义)指定类型、指定名称或满足指定条件的 Bean 时,才会加载被注解标记的配置。
-
场景:
-
依赖其他 Bean 的自动配置: 某个自动配置需要另一个 Bean 已经存在才能工作。例如,一个配置
JdbcTemplate
的自动配置类可能依赖于DataSource
Bean:@Configuration public class JdbcTemplateAutoConfiguration {@Bean@ConditionalOnBean(DataSource.class) // 只有容器中存在 DataSource Bean 时才创建 JdbcTemplatepublic JdbcTemplate jdbcTemplate(DataSource dataSource) {return new JdbcTemplate(dataSource);} }
确保
JdbcTemplate
只在有可用的DataSource
时才被创建。 -
按需配置扩展功能: 如果用户配置了某个特定的 Bean(如自定义的
MySpecialService
),则自动激活与之相关的额外功能或配置。
-
-
注意事项:
- 匹配条件可以指定 Bean 的类型 (
Class<?>
)、名称 (String name
) 或使用@ConditionalOnSingleCandidate
等注解进一步限定。 - Bean 定义顺序很重要! 被依赖的 Bean(
DataSource
)必须在依赖它的 Bean(JdbcTemplate
)之前被定义(或至少在同一批处理中能被检测到)。Spring Boot 的自动配置机制通过精心设计的加载顺序来解决这个问题。在自定义配置时需要注意潜在的循环依赖或顺序问题。
- 匹配条件可以指定 Bean 的类型 (
-
-
@ConditionalOnMissingBean
-
作用: 当且仅当 Spring 应用上下文 (IoC 容器) 中不存在(没有定义,也没有即将定义)指定类型、指定名称或满足指定条件的 Bean 时,才会加载被注解标记的配置。
-
场景:
-
提供默认 Bean 实现 (关键!): 这是 Spring Boot 自动配置的核心机制,也是用户覆盖默认配置的关键入口。自动配置类会使用此注解定义默认 Bean:
@Configuration public class DataSourceConfiguration {@Bean@ConditionalOnMissingBean // 关键注解!如果用户没有自定义 DataSource Bean...public DataSource dataSource() {// ...则 Spring Boot 自动创建一个默认的 DataSource (如 HikariCP)return new HikariDataSource();} }
-
用户自定义覆盖: 如果用户在自己的配置类中定义了相同类型的 Bean:
@Configuration public class MyCustomConfig {@Beanpublic DataSource myCustomDataSource() { // 自定义 DataSourcereturn new MySpecialDataSource();} }
此时,容器中已经存在一个类型为
DataSource
的 Bean (myCustomDataSource
)。因此,自动配置类中带有@ConditionalOnMissingBean
的dataSource()
方法将不会执行,用户自定义的DataSource
生效。这就是用户覆盖 Spring Boot 默认配置的方式。 -
避免重复定义: 确保只有在用户没有提供特定 Bean 时才提供默认实现。
-
-
总结
- 启动入口与
@SpringBootApplication
- 应用启动始于标注了
@SpringBootApplication
的主类。 @SpringBootApplication
是一个复合注解,核心包含:@SpringBootConfiguration
: 标记该类为配置类(本质上是@Configuration
)。@ComponentScan
: 扫描主类所在包及其子包下的@Component
,@Service
,@Repository
,@Controller
等注解的类,将其注册为 Bean。@EnableAutoConfiguration
: 这是触发自动配置的关键注解。
- 应用启动始于标注了
@EnableAutoConfiguration
的魔力- 这个注解利用 Spring Framework 的
@Import
机制导入了AutoConfigurationImportSelector
类。 AutoConfigurationImportSelector
实现了DeferredImportSelector
接口,它的核心方法是selectImports()
。
- 这个注解利用 Spring Framework 的
- 加载自动配置候选列表 (
spring.factories
/AutoConfiguration.imports
)AutoConfigurationImportSelector.selectImports()
方法的核心任务是从 classpath 下的特定位置加载所有可能应用的自动配置类的全限定名列表。- 加载位置 (优先级从上到下):
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
(Spring Boot 2.7+ 推荐方式,一行一个全类名)。META-INF/spring.factories
(旧方式,但广泛支持,EnableAutoConfiguration
key 下列出全类名)。(spring 3.x版本后没有了)
- Spring Boot 在
spring-boot-autoconfigure
jar 包的META-INF
目录下提供了大量内置的自动配置类(如DataSourceAutoConfiguration
,WebMvcAutoConfiguration
,JacksonAutoConfiguration
等)。 - 第三方 starter 也会在自己的 jar 包中提供相应的
spring.factories
或AutoConfiguration.imports
文件来声明其自动配置类。
- 自动配置类的过滤与筛选 (条件注解
@Conditional
)- 加载得到的候选自动配置类列表并不会全部生效。Spring Boot 的核心机制在于按需配置。
- 每个自动配置类上都标注了一个或多个
@Conditional
及其衍生注解(条件注解),用来判断这个配置类是否应该被加载、其内部的 Bean 是否应该被创建。 - 关键条件注解举例:
@ConditionalOnClass
: 当 classpath 下存在指定的类时才生效。@ConditionalOnMissingClass
: 当 classpath 下不存在指定的类时才生效。@ConditionalOnBean
: 当 Spring 容器中存在指定的 Bean 时才生效。@ConditionalOnMissingBean
: 当 Spring 容器中不存在指定的 Bean 时才生效(这是用户覆盖默认配置的关键)。@ConditionalOnProperty
: 当指定的配置属性具有特定值时才生效。@ConditionalOnResource
: 当 classpath 下存在指定的资源文件时才生效。@ConditionalOnWebApplication
/@ConditionalOnNotWebApplication
: 根据应用是否是 Web 应用来决定。@ConditionalOnJava
: 根据运行时的 Java 版本决定。
- 条件评估过程:
- Spring Boot 在启动时(具体在
ConfigurationClassPostProcessor
处理配置类期间)会创建一个ConditionEvaluator
。 - 对于每个候选的自动配置类,
ConditionEvaluator
会解析并评估其上的所有条件注解。 - 只有所有条件都满足的自动配置类,才会被真正导入 Spring 应用上下文,其内部的
@Bean
方法才有机会被执行。
- Spring Boot 在启动时(具体在
- 自动配置类的执行与 Bean 注册
- 通过条件评估筛选出来的自动配置类,会被当作标准的 Spring
@Configuration
类处理。 - Spring 容器会解析这些配置类:
- 执行类内部的
@Bean
方法,将返回的对象注册为 Spring 容器中的 Bean。 - 处理类上的其他注解(如
@ImportResource
,@PropertySource
等)。
- 执行类内部的
- 自动配置类的作用:
- 创建和配置应用运行所需的核心基础设施 Bean(如
DataSource
,JdbcTemplate
,DispatcherServlet
,ObjectMapper
,RestTemplateBuilder
等)。 - 根据 classpath 存在的依赖和用户配置的属性,智能地配置这些 Bean 的行为和默认值。
- 提供默认的属性绑定(通过
@EnableConfigurationProperties
+@ConfigurationProperties
)。
- 创建和配置应用运行所需的核心基础设施 Bean(如
- 通过条件评估筛选出来的自动配置类,会被当作标准的 Spring
- 用户配置优先
- 核心原则:自动配置是非侵入式的。用户定义的 Bean 总是优先于自动配置提供的默认 Bean。
- 这是通过
@ConditionalOnMissingBean
注解实现的。自动配置类在定义其默认 Bean 时,通常会加上@ConditionalOnMissingBean
。 - 如果用户在自己的
@Configuration
类中显式定义了一个相同类型的 Bean,那么这个用户定义的 Bean 会被注册到容器中。 - 当自动配置类执行到定义该默认 Bean 的方法时,
@ConditionalOnMissingBean
条件检测到容器中已存在该类型的 Bean(用户定义的),条件不满足,该方法不会执行,从而避免了注册默认 Bean。用户配置成功覆盖了自动配置。
- 属性配置 (
application.properties
/yml
)- 自动配置类大量使用
@ConfigurationProperties
来绑定外部配置(主要是application.properties
或application.yml
文件)。 - 这些属性用于覆盖自动配置提供的默认值。例如,配置
spring.datasource.url
,spring.datasource.username
,spring.datasource.password
来覆盖DataSourceAutoConfiguration
创建数据源时使用的默认值。
- 自动配置类大量使用
(三)自定义starter
自定义Starter是一种封装特定功能(如自动配置、依赖管理等)的便捷方式,让其他项目能快速集成。
自定义JWT Starter实现
这里以JWT为例,讲解如何实现jwt starter如何实现,通过实现自定义stater可以更好理解起步依赖和自动配置。我们需要实现一个jwt-spring-boot-starter
自定义starter,在其他模块导入这个依赖就可以直接调用JwtUtils
工具类里面的方法。
jwt-starter/
├── jwt-spring-boot-autoconfigure/ // 自动配置模块
│ ├── src/main/java
│ │ └── com/example/jwt
│ │ ├── JwtAutoConfiguration.java
│ │ ├── JwtProperties.java
│ │ └── JwtUtils.java
│ │
│ └── pom.xml
├── jwt-spring-boot-starter/ // Starter模块
│ └── pom.xml
-
创建
jwt-spring-boot-autoconfigure
模块,只留下pom.xml文件和scr文件,删除主启动类,跟上面结构一样(需要留下resource目录),并导入依赖<!-- jjwt 核心 --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.11.5</version></dependency><!-- 实现包 --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.11.5</version><scope>runtime</scope></dependency><!-- 使用 jackson 解析 JSON --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId><version>0.11.5</version><scope>runtime</scope></dependency>
-
创建
jwt-spring-boot-starter
模块,只留下pom.xml文件,再导入jwt-spring-boot-autoconfigure
模块依赖<dependency><groupId>com.example</groupId><artifactId>jwt-spring-boot-autoconfigure</artifactId><version>0.0.1-SNAPSHOT</version> </dependency>
-
在
jwt-spring-boot-autoconfigure
模块创建JwtProperties
配置类package com.example.jwt;import org.springframework.boot.context.properties.ConfigurationProperties;@ConfigurationProperties(prefix = "jwt") public class JwtProperties {// 密钥(Base64编码)private String secret = "c2VjcmV0LWtleS1mb3Itand0LWF1dGgtc3RhcnRlci1leGFtcGxl";// 有效时间(秒),默认1小时private long expiration = 3600;// 签发者private String issuer = "jwt-starter";// 是否开启调试模式private boolean debug = false;// Getters and Setterspublic String getSecret() {return secret;}public void setSecret(String secret) {this.secret = secret;}public long getExpiration() {return expiration;}public void setExpiration(long expiration) {this.expiration = expiration;}public String getIssuer() {return issuer;}public void setIssuer(String issuer) {this.issuer = issuer;}public boolean isDebug() {return debug;}public void setDebug(boolean debug) {this.debug = debug;} }
-
然后创建
JwtUtils
工具类package com.example;import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys;import java.security.Key; import java.util.Date; import java.util.HashMap; import java.util.Map;public class JwtUtils {private final JwtProperties jwtProperties;public JwtUtils(JwtProperties jwtProperties) {this.jwtProperties = jwtProperties;}// 生成JWT Tokenpublic String generateToken(String subject) {return generateToken(subject, new HashMap<>());}public String generateToken(String subject, Map<String, Object> claims) {long now = System.currentTimeMillis();return Jwts.builder().setClaims(claims).setSubject(subject).setIssuer(jwtProperties.getIssuer()).setIssuedAt(new Date(now)).setExpiration(new Date(now + jwtProperties.getExpiration() * 1000)).signWith(getSignKey(), SignatureAlgorithm.HS256).compact();}// 解析JWT Tokenpublic Claims parseToken(String token) {return Jwts.parserBuilder().setSigningKey(getSignKey()).build().parseClaimsJws(token).getBody();}// 验证Token是否有效public boolean validateToken(String token) {try {parseToken(token);return true;} catch (Exception e) {if (jwtProperties.isDebug()) {e.printStackTrace();}return false;}}// 从配置的密钥获取签名Keyprivate Key getSignKey() {byte[] keyBytes = Decoders.BASE64.decode(jwtProperties.getSecret());return Keys.hmacShaKeyFor(keyBytes);}// 获取Token中的主题(通常是用户ID)public String getSubjectFromToken(String token) {Claims claims = parseToken(token);return claims.getSubject();}// 获取Token中的自定义声明public Object getClaimFromToken(String token, String claimName) {Claims claims = parseToken(token);return claims.get(claimName);} }
-
创建
JwtAutoConfiguration
自动配置的配置类import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;@Configuration // 启用并注册配置属性类 // application.yml 或 application.properties 中的配置项自动绑定到一个 Java Bean 类中。 @EnableConfigurationProperties(JwtProperties.class) public class JwtAutoConfiguration {// 当spring容器不存在这个bean时,才注入该Bean@ConditionalOnMissingBean@Beanpublic JwtUtils jwtUtils (JwtProperties jwtProperties){return new JwtUtils(jwtProperties);} }
-
创建目录文件
src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
导入自己的JwtAutoConfiguration
全路径com.example.JwtAutoConfiguration
-
然后是测试,另外新建一个模块,导入我们刚才写好的依赖
<dependency><groupId>com.example</groupId><artifactId>jwt-spring-boot-starter</artifactId><version>0.0.1-SNAPSHOT</version> </dependency>
@SpringBootTest public class TestLogin {@Autowiredprivate JwtUtils jwtUtils;@Testvoid JwtStarterTest (){HashMap<String, Object> map = new HashMap<>();map.put("username","tom");map.put("password","123123");String jwt = jwtUtils.generateToken("0001",map);System.out.println(jwt);} }
最后这里可以看见jwt-spring-boot-starter
生效了
通过本文的讲解,相信你已经对 Spring Boot
的两大核心机制 —— 起步依赖(Starter
) 和 自动装配(Auto-Configuration
) 有了更深入的理解。
Spring Boot
的“开箱即用”并非魔法,而是建立在 Spring
强大的容器管理和条件化配置基础上的工程智慧。理解这些底层原理,不仅能帮助你更好地使用 Spring Boot
,还能在遇到复杂问题时更快地定位和解决。
如果你觉得这篇文章对你有帮助,欢迎点赞、收藏并分享给更多需要的小伙伴。这里是 盛码笔记,我们下期再见!