前言
用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核心特性——你所要知道的自动装配》