如何写自己的springboot starter?自动装配原理是什么?
官方文档地址:https://docs.spring.io/spring-boot/docs/2.6.13/reference/html/features.html#features.developing-auto-configuration
1. 不用starter有什么弊端?
- 我们开发需要引入maven依赖,如果我们需要的依赖又有它自身所需要的依赖。我们需要同时去引入才能去使用,这种模式让我们苦不堪。例如要在项目中使用 Spring Data JPA 进行数据库操作,需要手动添加 Spring Data JPA、Hibernate、数据库驱动等相关依赖,这不仅需要开发者了解每个依赖的具体信息,还容易出现版本不兼容的问题,因为不同版本的依赖之间可能存在冲突。
- 配置麻烦,在SSM时期,大量的配置让我们晕头转向,没有starter的自动配置功能,需要手动编写大量的配置类和配置文件来启用和配置各种功能。例如,配置 Spring Data JPA 时,需要手动配置数据源、实体管理器工厂、事务管理器等
- 如果另一个项目也需要同时去做同样的功能,如果不用starter只能使用CV大法把之前配置的都移过来,CV有可能出错,也让项目臃肿。
针对以上缺点,starter诞生它只需要引入一个依赖,搞定!
常见的starter例如:
spring-boot-starter
spring-boot-starter-web
spring-boot-starter-test
spring-cloud-starter-alibaba-nacos-discovery
druid-spring-boot-starter
2. springboot自动装配
要想自定义一个starter就要知道springboot是怎么样自动装配bean的
2.1 核心注解:
- @SpringBootApplication:这是一个组合注解,相当于同时使用了 @Configuration、@EnableAutoConfiguration 和 @ComponentScan 注解。它标记了主应用程序类,并告诉 Spring Boot 开始组件扫描、自动配置和装配。
- @EnableAutoConfiguration:该注解用于启用 Spring Boot 的自动配置功能。它会根据应用程序的依赖关系和当前环境,自动注册所需的 bean。
- @ComponentScan:该注解用于启用组件扫描,以便 Spring Boot 可以自动发现和注册标有 @Component、@Service、@Repository 和 @Controller 注解的类。
- @ConditionalOnClass 和 @ConditionalOnMissingClass:这两个条件化注解用于根据类路径上是否存在特定的类来决定是否注册 bean。@ConditionalOnClass 在类路径上存在指定类时生效,而 @ConditionalOnMissingClass 在类路径上不存在指定类时生效。
- @ConditionalOnBean 和 @ConditionalOnMissingBean:这两个条件化注解用于根据是否存在特定的 bean 来决定是否注册 bean。@ConditionalOnBean 在容器中存在指定的 bean 时生效,而 @ConditionalOnMissingBean 在容器中不存在指定的 bean 时生效。
- @ConditionalOnProperty:该条件化注解用于根据配置属性的值来决定是否注册 bean。它可以根据配置文件中的属性值来决定是否启用或禁用特定的 bean。
tips: 如果官方给你的注解不够满足需求可以自己创建一个Conditional,@Conditional(value = TestCondition.class)
public class TestCondition implements Condition {@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {return true;}
}
2.2 自动装配大白话
- springboot启动类注解@SpringBootApplication是一个组合注解 里面包含了@EnableAutoConfiguration,即开启自动装配
- @Import(AutoConfigurationImportSelector.class),导入AutoConfigurationImportSelector,并通过 selectImports 方法读取 META-INF/spring.factories 文件中配置的全类名
tips: 从spring boot2.7开始,慢慢不支持META-INF/spring.factories文件了需要导入的自动配置类可以放在
/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports - 并按照@ConditionalOnClass这样条件过滤的注解,如果满足条件注入bean
3. starter前知
3.1 取名该怎么样取?
spring官方文档上是这样说的 https://docs.spring.io/spring-boot/docs/2.0.0.M5/reference/htmlsingle/#boot-features-custom-starter-naming
翻译:
请确保为启动器提供合适的名称空间。不要以spring-boot作为模块名的开头,即使您使用的是不同的Maven groupId。我们可能会在将来为您自动配置的东西提供官方支持。
这是一条经验法则。假设您正在为“acme”创建一个启动器,将自动配置模块命名为acme-spring-boot-autoconfigure,将启动器命名为acme-spring-boot-starter。如果只有一个模块结合了这两个模块,那么使用acme-spring-boot-starter。
此外,如果启动器提供了配置键,请为它们使用适当的名称空间。特别是,不要在Spring Boot使用的命名空间(例如server、management、Spring等)中包含键。这些都是“我们的”,我们可能会在将来改进/修改它们,这样可能会破坏你的东西。
确保触发元数据生成,以便也为您的键提供IDE帮助。您可能需要查看生成的元数据(META-INF/spring-configuration-metadata.json),以确保正确地记录了密钥。
一般来说取名 xxx-spring-boot-starter就可以了
3.2 starter项目目录介绍
- maven.spring-cloud-starter-alibaba-nacos-discovery
这个是有关于maven的文件 - additional-spring-configuration-metadata.json
{"properties": [{"name": "spring.cloud.loadbalancer.nacos.enabled","type": "java.lang.Boolean","defaultValue": false,"description": "Integrate LoadBalancer or not."}
]}
这个文件是配置元数据,很多时候我们可能会不知道这个starter有哪些是可以配置的,就可以来找这个文件,name就是属性名,defaultValue是默认值,description是描述。idea中配置文件里的补全提示也是读取这里的进行提示
- MANIFEST.MF
该文件包含了该 JAR 包的版本、创建人和类搜索路径等信息。 - spring.factories
spring.factories文件里的内容就是我们需要自启动装配的bean全路径
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
官方tips:不要让这些类被组件扫描到
还有一些另外可能出现的键
org.springframework.boot.autoconfigure.EnableAutoConfiguration:用于自动配置类
org.springframework.context.ApplicationContextInitializer:应用上下文初始化器org.springframework.context.ApplicationListener:应用事件监听器
org.springframework.boot.SpringApplicationRunListener:应用运行监听器
org.springframework.boot.env.PropertySourceLoader:属性源加载器
org.springframework.boot.diagnostics.FailureAnalyzer:失败分析器
org.springframework.boot.env.EnvironmentPostProcessor:环境后处理器
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter:自动配置导入过滤器
org.springframework.boot.autoconfigure.AutoConfigurationPackage:自动配置包
4. 实战需求来了
要实现一个starter,打印请求详细信息。
- 创建request-log-spring-boot-starter项目,打包方式为jar,引入依赖
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><scope>provided</scope> <!-- 确保不重复引入 --></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional> <!-- 生成配置元数据 --></dependency></dependencies>
- 创建RequestLogProperties类读取配置文件
@ConfigurationProperties(prefix = "request-log")
public class RequestLogProperties {private boolean enabled = true; // 是否启用日志private boolean logHeaders = true; // 是否记录请求头private boolean logBody = false; // 是否记录请求体(默认关闭,可能影响性能)public boolean isEnabled() {return enabled;}public void setEnabled(boolean enabled) {this.enabled = enabled;}public boolean isLogHeaders() {return logHeaders;}public void setLogHeaders(boolean logHeaders) {this.logHeaders = logHeaders;}public boolean isLogBody() {return logBody;}public void setLogBody(boolean logBody) {this.logBody = logBody;}
}
- resources下新建META-INF文件夹
- META-INF文件夹下新建spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.example.config.RequestLogAutoConfiguration
RequestLogAutoConfiguration类被spring.factories引入不需要@Configuration注解了
- 编写RequestLogAutoConfiguration类,加入条件判断
@ConditionalOnProperty(name = "request-log.enable", matchIfMissing = true)
@EnableConfigurationProperties(RequestLogProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
- 写RequestLoggingFilter类继承OncePerRequestFilter实现doFilterInternal进行业务逻辑
- 完工,测试
5. 代码提交到git上了:
https://gitee.com/isyuesen/request-log/tree/main/-