• 【SpringBoot】之创建自定义 SpringBoot-Starter


    目录


    一、SpringBoot Starter 简介


    1、什么是 SpringBoot Starter

    SpringBoot 的出现让我们能够抛弃以前复杂繁杂的配置,转而将组件各种配置统一集成进 starter 中,在使用的使用只需要在 maven 中引入 starter 依赖,SpringBoot 就能自动扫描到要加载的信息并启动相应的默认配置。

    Starter 让我们摆脱了各种依赖库的处理以及需要配置各种信息的困扰。因为 SpringBoot 会自动通过 classpath 路径下的类发现需要的 Bean,对其进行自动装配并注册进 IOC 容器。

    SpringBoot 提供了许多我们日常开发中在各种场景所需的 spring-boot-starter 依赖模块。所有这些依赖模块都会遵循着约定成俗的默认配置,并允许我们调整这些配置,即遵循约定大于配置的理念。

    想了解更多关于 SpringBoot 的知识以及 SpringBoot 是如何进行自动装配组件的,可以参考我的另一篇博客:SpringBoot】之自动配置原理分析(源码级别)

    2、什么场景下需要自定义 Starter

    在日常的开发工作中,经常会有一些独立于业务之外的配置模块,如果我们将其放到一个特定的包下,然后另一个工程需要复用这块功能的时候,就需要将代码硬拷贝到另一个工程,重新集成一遍,这样做非常的麻烦。

    但如果我们将这些可独立于业务代码之外的功配置模块封装成独立的 Starter,复用的时候只需要将其在 pom 中引用依赖即可,因为 SpringBoot 的自动装配会为我们完成模块的装配,极大地方便了我们开发。

    常见场景如下:

    • 构建通用模块:比如短信、邮件发送模块
    • 基于 AOP 技术实现日志切面
    • 处理分布式雪花 ID:将 Long 型转为 String 型、使用 jackson2/fastjson 解决精度问题

    二、创建自定义的 SpringBoot Starter


    1、Starter 命令规范

    每个 Starter 都会遵循标准的命名规范,其中分为官方 Starter 的命名方法和自定义 Starter 的命名方式:

    SpringBoot 官方命名方式:

    • 格式:spring-boot-starter-{模块名}
    • 举例:spring-boot-starter-web

    自定义命名方式:

    • 格式:{模块名}-spring-boot-starter
    • 举例:mystarter-spring-boot-starter

    2、Starter 创建步骤

    我们先回顾一下 SpringBoot 的自动装配原理:

    • Spring Boot 在启动时会去 classpath 中中寻找 resources/META-INF/spring.factories 文件;
    • 根据 spring.factories 配置加载 AutoConfigure 类;
    • 根据 @Conditional 注解的条件,进行自动配置并将 Bean 注入 Spring Context。

    这个原理相当于给了我们一个借鉴,只要我们遵循了这个规范我们就可以很简单的进行创建自定义的 Starter

    步骤如下:

    1)创建 Maven 项目

    创建一个空的 Maven 项目:

    Starter 不需要 main 启动类,一般创建空的 Maven 项目最合适,如果有生成启动类,直接删除掉。

    2)添加依赖

    下面市场完整的依赖:

    
    <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.0modelVersion>
    
        <parent>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-parentartifactId>
            <version>2.2.1.RELEASEversion>
            <relativePath/> 
        parent>
    
        <groupId>com.wtygroupId>
        <artifactId>email-spring-boot-starterartifactId>
        <version>1.0.0-SNAPSHOTversion>
    
        <properties>
            <maven.compiler.source>8maven.compiler.source>
            <maven.compiler.target>8maven.compiler.target>
        properties>
    
        <dependencies>
            
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-autoconfigureartifactId>
            dependency>
            
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-configuration-processorartifactId>
                <optional>trueoptional>
            dependency>
            
            
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
                <optional>trueoptional>
            dependency>
        dependencies>
    
    project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    这里需要注意两点:

    • spring-boot-configuration-processor 并不是必须的,只是引入之后,在 yml 配置文件中编写配置时会有提示;而设置 标签的值为 true 表示,两个项目直接依赖不传递,例如:项目A依赖 a.jar,项目B依赖项目A,则项目B不依赖 a.jar;
    • 由于 starter 是没有 main 方法入口的,所以需要去除 pom 文件中 maven 打包插件 spring-boot-maven-plugin。

    2)编写相关属性配置类

    @Data
    @ConfigurationProperties(prefix = "email.config")
    public class EmailProperties {
    
        private String username = "";
    
        private String password = "";
    
        private String host = "smtp.163.com";
    
        private Integer port = 25;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    3)编写 Starter 的功能类

    @Data
    public class EmailService {
    
        private EmailProperties emailProperties;
    
        /**
         * 模拟邮件发送功能
         */
        public void sendEmail() {
            System.out.println("Sending email... \n" +
                    "username: " + emailProperties.getUsername() + "\n" +
                    "password: " + emailProperties.getPassword() + "\n" +
                    "host: " + emailProperties.getHost() + "\n" +
                    "port: " + emailProperties.getPort());
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    4)编写自动配置类

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass({EmailService.class})
    @EnableConfigurationProperties({EmailProperties.class})
    public class EmailAutoConfiguration {
    
        @Autowired
        private EmailProperties emailProperties;
    
        @Bean
        @ConditionalOnMissingBean
        public EmailService emailService() {
            EmailService emailService = new EmailService();
            emailService.setEmailProperties(emailProperties);
            return emailService;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    解析:

    • @Configuration 注解修饰表示该类为配置类,会注入到容器中,proxyBeanMethods = false 表示使用 Lite 轻量级模式
    • @ConditionalOnClass 注解表示只有存在 EmailService.class 类时才生效自动配置类;
    • @EnableConfigurationProperties 注解使我们定义的 EmailProperties.class 属性配置类生效。

    扩展知识:Full 全模式和 Lite 轻量级模式

    @Configuration 注解的参数 proxyBeanMethods 表示设置 Bean 的代理模式:

    • Full 全模式(proxyBeanMethods = true,默认值):同一配置类下,当直接调用 @Bean 修饰的方法注入的对象,则调用该方法会被代理,从 ioc 容器中取 bean 实列,所以实列是一样的。即单实例对象,在该模式下 SpringBoot 每次启动都会判断检查容器中是否存在该组件;
    • Lite 轻量级模式(proxyBeanMethods = false):同一配置类下,当直接调用 @Bean 修饰的方法注入的对象,则调用该方法不会被代理,相当于直接调用一个普通方法,会有构造方法,但是没有 bean 的生命周期,返回的是不同的实例。

    注意: proxyBeanMethods 只是为了让使用 @Bean 注解的方法被代理,而不是对 @Bean 设置其为单例还是多例。

    什么时候用Full全模式,什么时候用Lite轻量级模式:

    • 当在你的同一个 Configuration 配置类中,注入到容器中的 bean 实例之间有依赖关系时,建议使用 Full 全模式;
    • 当在你的同一个 Configuration 配置类中,注入到容器中的 bean 实例之间没有依赖关系时,建议使用 Lite 轻量级模式,以提高 springboot 的启动速度和性能。

    5)编写 spring.factories 文件

    在 src/main/resources 下添加 META-INF/spring.factories 文件

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
      com.wty.starter.config.EmailAutoConfiguration
    
    • 1
    • 2

    把自动配置类 EmailAutoConfiguration 配置到org.springframework.boot.autoconfigure.EnableAutoConfiguration 的 key 下,SpringBoot 会自动加载该文件并根据条件装配。

    6)打包

    在 Maven 插件的 Lifecycle 下点击 install,对项目进行打包生成 starter 包:email-spring-boot-starter

    3、测试验证

    在另一个项目中引入我们自定义的 starter 依赖:

    <dependency>
        <groupId>com.wtygroupId>
        <artifactId>email-spring-boot-starterartifactId>
        <version>1.0.0-SNAPSHOTversion>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在 yml 配置相应的属性(编写的过程中会弹出属性配置提示):

    email:
      config:
        username: xxx@163.com
        password: 123456
        host: smtp.163.com
        port: 25
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    编写测试接口:

    @RestController
    @RequestMapping("/starter")
    public class StarterController {
    
        @Autowired
        private EmailService emailService;
    
        @GetMapping("/email")
        public void testEmail() {
            emailService.sendEmail();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    得到结果:

    Sending email... 
    username: xxx@163.com
    password: 123456
    host: smtp.163.com
    port: 25
    
    • 1
    • 2
    • 3
    • 4
    • 5
  • 相关阅读:
    [NOI2020统一省选 A] 组合数问题 (推式子)
    FPGA面试题(7)
    java基础19
    Introduction to Assembly and RISC-V
    项目7-音乐播放器5+注册账号
    【Unity】RenderFeature应用(简单场景扫描效果)
    (算法设计与分析)第三章动态规划-第一节3:动态规划之使用“找零钱”问题说明最优子结构如何解决
    【C语言】字符串加密解密,字符串左旋,杨氏矩阵,删除字符串中出现次数最少的字符
    【EI会议征稿】第七届大数据与应用统计国际学术研讨会(ISBDAS 2024)
    Kotlin基础——DSL
  • 原文地址:https://blog.csdn.net/aiwangtingyun/article/details/126557756