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

2023年 8月 1日 52.6k 0

前言

用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,进行打包。

image.png

引用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,输出如下:

image.png

接下来往application.yml中添加reading配置,让自动装配生效

reading:  
    enable: true

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

image.png

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

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论