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

相关推荐
程序媛小果几秒前
基于java+SpringBoot+Vue的宠物咖啡馆平台设计与实现
java·vue.js·spring boot
追风林6 分钟前
mac m1 docker本地部署canal 监听mysql的binglog日志
java·docker·mac
芒果披萨20 分钟前
El表达式和JSTL
java·el
duration~1 小时前
Maven随笔
java·maven
zmgst1 小时前
canal1.1.7使用canal-adapter进行mysql同步数据
java·数据库·mysql
跃ZHD1 小时前
前后端分离,Jackson,Long精度丢失
java
blammmp2 小时前
Java:数据结构-枚举
java·开发语言·数据结构
暗黑起源喵2 小时前
设计模式-工厂设计模式
java·开发语言·设计模式
WaaTong2 小时前
Java反射
java·开发语言·反射
狂放不羁霸2 小时前
idea | 搭建 SpringBoot 项目之配置 Maven
spring boot·maven·intellij-idea