SpringBoot核心特性——手写一个自己的starter
前言
用SpringBoot开发会发现集成功能是很方便的,比如需要Web相关功能,就只需要引入spring-boot-starter-web、集成单元测试则引入spring-boot-starter-test...这样一个个starter极大简化了开发成本,增强了开发效率。本文则来讲解一下如何封装一个自己的starter。
创建starter
maven依赖
新建一个thinking-starter工程,pom.xml依赖如下:
4.0.0 org.springframework.boot spring-boot-starter-parent 2.7.14 geek.springboot.starter thinking-starter jar 8 8 UTF-8 org.projectlombok lombok 1.16.18 org.springframework.boot spring-boot-autoconfigure
配置映射
创建一个ReadingConfig,映射application.yml或application.properties中的相关配置:
package geek.springboot.starter.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; /** * 读取application.properties中reading相关的配置 */ @Data @ConfigurationProperties(prefix = "reading") public class ReadingConfig { // 类型 private String type; }
在resources目录下新建application.properties
,默认配置type为txt,代码如下:
reading.type=txt
默认服务
新建一个Reading接口:
package geek.springboot.starter.service; public interface Reading { void reading(); }
新建一个Reading接口实现类:
package geek.springboot.starter.service.impl; import geek.springboot.starter.config.ReadingConfig; import geek.springboot.starter.service.Reading; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; @Slf4j @AllArgsConstructor public class ReadingService implements Reading { // reading相关配置类 private ReadingConfig readingConfig; @Override public void reading() { log.info("start reading... type is {}", this.readingConfig.getType()); } }
自动装配
新建一个ReadingConfiguration,这个类是自动装配的核心:
package geek.springboot.starter.configuration; import geek.springboot.starter.config.ReadingConfig; import geek.springboot.starter.service.Reading; import geek.springboot.starter.service.impl.ReadingService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @EnableConfigurationProperties(ReadingConfig.class) @ConditionalOnProperty(name = "reading.enable", havingValue = "true") // 当存在reading.enable属性,且其值为true时,才初始化该Bean public class ReadingConfiguration { @Autowired private ReadingConfig readingConfig; // 若当前IOC容器中没有Reading接口实现时,提供一个默认的Reading实现 @Bean @ConditionalOnMissingBean(Reading.class) public Reading readingService() { return new ReadingService(this.readingConfig); } }
如果对@EnableConfigurationProperties不熟悉,可以看这里《SpringBoot核心特性——万字拆解外部配置》
如果对@ConditionalOnProperty不熟悉,可以看这里《SpringBoot核心特性——你所要知道的自动装配》
自动装配注册
新建META-INF/spring.factories
,提醒SpringBoot在启动时,别忘了这还有个自动装配的Configuration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=geek.springboot.starter.configuration.ReadingConfiguration
maven打包
到这里其实简易版starter就差不多了,整体思路就是只要application.yml或application.properties配置了reading.enable=true,那么我就会往Spring提供一个Reading实现,业务方可以在业务逻辑中注入这个Reading实现去做一些事情。做到这里,剩下来的就是进行Maven打包,让别的项目可以依赖该starter。
IDEA点击Maven菜单,然后点击Lifecycle下的install,进行打包。
引用starter
切换到业务工程,pom.xml依赖添加我们的starter
geek.springboot.starter thinking-starter 1.0-SNAPSHOT
新建一个ReadingHandler,代码如下:
package geek.springboot.application.service; import geek.springboot.starter.service.Reading; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; @Slf4j @Service public class ReadingHandler { /** * 因为若application.yml中没有reading相关配置,starter中自动装配不会生效,默认的Reading实现不会被初始化,会导致在SpringBoot启动时抛异常。 * 所以这里required设置为false,表示ReadingHandler初始化时,不强制要求一定要注入Reading实现 */ @Autowired(required = false) private Reading reading; @PostConstruct public void init() { // 若当前Reading实现不为空,调用其reading()方法,否则打印reading is null... if (this.reading != null) { this.reading.reading(); } else { log.error("reading is null..."); } } }
启动SpringApplication,输出如下:
接下来往application.yml中添加reading配置,让自动装配生效
reading: enable: true
重启SpringApplication,可以看到自动装配生效了,控制台输出如下:
重写starter默认配置
之前创建starter时,reading.type默认配置为reading.type=txt,我们想重写默认配置也是很简单的,只需要在当前项目application.yml中稍作添加
reading: enable: true type: json # 重写starter默认配置
重启SpringApplication,输出如下,可以看到默认配置已经成功重写
2023-07-30 15:12:01.014 INFO 96828 --- [main] g.s.starter.service.impl.ReadingService : start reading... type is json
重写starter默认实现
如果觉得starter默认的Reading实现不够好,那么我们也可以自定义Reading实现。因为starter构造Reading实现那里加上了@ConditionalOnMissingBean(Reading.class)
,所以我们只需要在我们当前工程自行实现Reading接口,并将其注册到SpringIOC中,则starter中默认Reading实现将不会被初始化。
新建一个MyReadingService,代码如下:
package geek.springboot.application.service; import geek.springboot.starter.config.ReadingConfig; import geek.springboot.starter.service.Reading; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Slf4j @Service public class MyReadingService implements Reading { @Autowired private ReadingConfig readingConfig; @Override public void reading() { log.info("my reading service start reading... type is {}", this.readingConfig.getType()); } }
重启SpringApplication,控制台输出如下,可以看到ReadingHandler中注入的不是默认的ReadingService了,而是我们创建的MyReadingService
2023-07-30 15:27:57.631 INFO 92232 --- [main] g.s.a.service.MyReadingService : my reading service start reading... type is json
实现一个自己的@EnableXXX
在application.yml配置reading.enable=true这样的方式让自动装配生效,其实不够优雅。那么我们也可以像别的starter那样提供@EnableXXX(@EnableScheduling、@EnableAsync、@EnableCaching...)注解,然后在SpringApplication启动类加上@EnableXXX,让我们的自动装配生效。
回到starter,创建一个EnableReading注解,代码如下:
package geek.springboot.starter.annotation; import geek.springboot.starter.configuration.ReadingSelector; import org.springframework.context.annotation.Import; import java.lang.annotation.*; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented // @Import作用是往SpringIOC中导入一个类,这里即导入ReadingSelector @Import(ReadingSelector.class) public @interface EnableReading { }
创建ReadingSelector,用以取代之前的ReadingConfiguration
package geek.springboot.starter.configuration; import geek.springboot.starter.config.ReadingConfig; import geek.springboot.starter.service.Reading; import geek.springboot.starter.service.impl.ReadingService; import org.springframework.beans.factory.annotation.Autowired; 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 @EnableConfigurationProperties(ReadingConfig.class) public class ReadingSelector { @Autowired private ReadingConfig readingConfig; // 当SpringIOC容器中不存在Reading实现时,才往Spring中初始化ReadingService作为Reading接口的实现 @Bean @ConditionalOnMissingBean(Reading.class) public Reading readingService() { return new ReadingService(this.readingConfig); } }
重新将starter打包到本地Maven仓库,并记得在业务工程中刷新一下maven依赖。
接下来验证我们的@EnableReading注解
回到业务工程,为了印证@EnableReading注解是否可行,稍作一下调整
application.yml删除reading.enable: true,让ReadingConfiguration自动装配无效
reading: type: json # 重写starter默认配置
ReadingHandler稍作调整,表示ReadingHandler初始化时,Spring一定要注入一个Reading实现
@Autowired // 之前是@Autowired(required = false) private Reading reading;
以及记得把MyReadingService中的@Service注解删除,以防我们的自定义Reading实现被Spring加载
最后给SpringApplication启动类加上@EnableReading
@SpringBootApplication @EnableReading public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
启动SpringApplication,可以看到通过标记@EnableReading来让自动装配生效也是成功的
2023-07-30 15:58:24.308 INFO 87024 --- [main] g.s.starter.service.impl.ReadingService : start reading... type is json
结尾
本文章源自《Learn SpringBoot》专栏,感兴趣的话还请关注点赞收藏.
上一篇文章:《# SpringBoot核心特性——你所要知道的自动装配》