SpringBoot核心特性——手写一个自己的starter

前言

用SpringBoot开发会发现集成功能是很方便的,比如需要Web相关功能,就只需要引入spring-boot-starter-web、集成单元测试则引入spring-boot-starter-test...这样一个个starter极大简化了开发成本,增强了开发效率。本文则来讲解一下如何封装一个自己的starter。

创建starter

maven依赖

新建一个thinking-starter工程,pom.xml依赖如下:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>  
<project xmlns="http://maven.apache.org/POM/4.0.0"  
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  
<modelVersion>4.0.0</modelVersion>  

    <parent>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-starter-parent</artifactId>  
    <version>2.7.14</version>  
    <relativePath />  
    </parent>
  
    <groupId>geek.springboot.starter</groupId>  
    <artifactId>thinking-starter</artifactId>  
    <packaging>jar</packaging>
  
    <properties>  
        <maven.compiler.source>8</maven.compiler.source>  
        <maven.compiler.target>8</maven.compiler.target>  
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  
    </properties>  
  
    <dependencies>  
        <!-- lombok依赖 -->
        <dependency>  
            <groupId>org.projectlombok</groupId>  
            <artifactId>lombok</artifactId>  
            <version>1.16.18</version>  
        </dependency>
        <!-- 自动配置 -->
        <dependency>  
            <groupId>org.springframework.boot</groupId>  
            <artifactId>spring-boot-autoconfigure</artifactId>  
        </dependency>  
    </dependencies>  
  
</project>

配置映射

创建一个ReadingConfig,映射application.yml或application.properties中的相关配置:

kotlin 复制代码
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,代码如下:

ini 复制代码
reading.type=txt

默认服务

新建一个Reading接口:

csharp 复制代码
package geek.springboot.starter.service;  
  
public interface Reading {  
  
    void reading();  
  
}

新建一个Reading接口实现类:

java 复制代码
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,这个类是自动装配的核心:

kotlin 复制代码
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

ini 复制代码
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

xml 复制代码
<dependencies>  

    <dependency>  
        <groupId>geek.springboot.starter</groupId>  
        <artifactId>thinking-starter</artifactId>  
        <version>1.0-SNAPSHOT</version>  
    </dependency>  
  
</dependencies>

新建一个ReadingHandler,代码如下:

kotlin 复制代码
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配置,让自动装配生效

yaml 复制代码
reading:  
    enable: true

重启SpringApplication,可以看到自动装配生效了,控制台输出如下:

重写starter默认配置

之前创建starter时,reading.type默认配置为reading.type=txt,我们想重写默认配置也是很简单的,只需要在当前项目application.yml中稍作添加

yaml 复制代码
reading:  
    enable: true  
    type: json # 重写starter默认配置

重启SpringApplication,输出如下,可以看到默认配置已经成功重写

python 复制代码
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,代码如下:

java 复制代码
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

lua 复制代码
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注解,代码如下:

kotlin 复制代码
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

kotlin 复制代码
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依赖。

验证@EnableXXX

回到业务工程,为了印证@EnableReading注解是否可行,稍作一下调整

application.yml删除reading.enable: true,让ReadingConfiguration自动装配无效

yaml 复制代码
reading:  
    type: json # 重写starter默认配置

ReadingHandler稍作调整,表示ReadingHandler初始化时,Spring一定要注入一个Reading实现

ini 复制代码
@Autowired   // 之前是@Autowired(required = false)
private Reading reading;

以及记得把MyReadingService中的@Service注解删除,以防我们的自定义Reading实现被Spring加载

最后给SpringApplication启动类加上@EnableReading

less 复制代码
@SpringBootApplication
@EnableReading  
public class Application {  
  
    public static void main(String[] args) {  
        SpringApplication.run(Application.class, args);  
    }  
  
}

启动SpringApplication,可以看到通过标记@EnableReading来让自动装配生效也是成功的

python 复制代码
2023-07-30 15:58:24.308  INFO 87024 --- [main] g.s.starter.service.impl.ReadingService  : start reading... type is json

结尾

本文章源自《Learn SpringBoot》专栏,感兴趣的话还请关注点赞收藏.

上一篇文章:《# SpringBoot核心特性------你所要知道的自动装配

相关推荐
aramae14 小时前
模拟实现strcpy(字符串拷贝)(C语言)
java·c语言·开发语言·算法
IT毕设实战小研14 小时前
基于大数据的跨国外派人员适应满意度与留存影响因素可视化分析
android·java·大数据·python·django·课程设计
letisgo514 小时前
JAVA 高级进阶10篇《消息队列实战:RocketMQ/Kafka选型与“不丢不重有序“三连解》
java·面试·kafka·消息队列·rocketmq
2401_8504811714 小时前
UVA101
java
Lyyaoo.14 小时前
【回溯】【中等】全排列
java·数据结构·算法
xixingzhe214 小时前
spring boot对接Dify
java·spring boot·dify
数据狐(Datafox)14 小时前
京东商品详情API接口解析(附 JSON 样例)
java·开发语言·json
步行cgn15 小时前
Spring Boot 指定数据来源详解
spring boot·后端·python
Wang's Blog15 小时前
Java框架快速入门: Spring Security+OAuth2之深度定制UserDetailsService与密码无缝升级
java·开发语言·spring
土司大王15 小时前
LeetCode 79 单词搜索:Java 回溯模板、网格 DFS 与剪枝优化
java·算法·leetcode·深度优先