Spring Boot 自定义 Starter

Spring Boot 自定义 Starter

SpringBoot-Starter是什么?

​ 随着Java的不断发展,Spring成为一个Java工程师必备的工作技能,虽然Spring的出现为开发人员解决了bean管理的烦恼,但是其配置随着Java的使用者不断增加,其配置变得及其复杂。使用Spring配置一个web应用程序(单纯的配置一个简单web程序),根据开发者的熟练度,最少也需要20~60分钟左右。但是如果一个应用所依赖的bean非常多时,这将是一件非常麻烦的事情。于是SpringBoot为简化这一过程定制了SpringBoot-Strater机制,让开发人员更加快速的构建Java的大型应用程序,减少开发人员对配置文件编写复杂的工作。这也是SpringBoot自发展以来,能快速的得到开发者响应,并使用的原因。

​ 简单来说SpringBoot-Starter是利用类似于JavaSPI(Service Provider Interface)机制的通过文件的形式来动态注入Bean到Spring的IOC容器中,从而完成开发者所需要注入的Bean实例,来实现用户的定制化功能。

SPI(Service Provider Interface)是什么?

​ SPI(Service Provider Interface),是JDK内置的一种 服务提供发现机制,可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用,比如java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,MySQL和PostgreSQL都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。Java中SPI机制主要思想是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,其核心思想就是 解耦

SPI调用原理

  1. 服务调用方调用标准的服务提供接口。

  2. 加载服务提供的配置文件。

  3. 通过加载结果调用对应的服务提供者。

    ​ 从上图以及运行原理,我们不难看出SPI的服务机制就是,提前准备不同接口所需要的服务方法,然后我们可通过配置文件的方式即可动态的调用我们所需要的服务提供接口,最常见的使用方式是在JDBC4.0后的引入,在4.0之前 Class.forName("com.mysql.jdbc.Driver");,我们需要通过代码利用反射来加载我们所需要调用的类,在4.0之后,我们仅仅需要准备配置文件到配置文件中,读者有兴趣可查看mysql连接jar包下的META-INF/services目录,其底层应用到了jdkServiceLoader类的load()方法,读者有兴趣也可看看该类的加载方法。

SpringBoot中的SPI

​ 在上面,我们已经解释了什么是SPI,同时也提到了什么是在SpringBoot-Starter中也引用了类似与Jdk所提供的SPI机制来动态注入Bean实例。那么Spring-Boot是如何引入SPI机制的呢?

SpringBoot加载bean的步骤原理

  1. 在启动类上添加@SpringBootApplication注解。
  2. 加载spring-factories的配置文件。
  3. 传递需要加载的bean到spring工厂
  4. Spring工厂获取到需要配置到容器的bean

​ 这也是SpringBoot启动加载实例Bean的底层设计,其实在SpringBoot中,我们自需要关注写一个SpringBoot启动类即可,其它操作都是由SpringBoot的底层源码来给我们实现,对于我们需要自定义的Starter,其实通过上面的流程我们也不难理解自需要定义一个spring-factories文件即可完成自定义的Starter的启动,下面我们就来了解如何自定义Starter

SpringBoot自定义Starter实战

​ 通过上面的了解,我们认识了什么是SPI,以及SpringBoot中引入的类似SPI机制来自定义Starter,接下来将通过实战方式,让读者快速的了解SpringBoot自定义Starter。其主要步骤主要有以下几个步骤完成。

  1. 编辑相关业务的核心代码模块(类似与mybatis-plus的业务逻辑处理流程)
  2. 配置业务流程所需要的自动配置类(类似与mybatis-plus使用前需要配置数据库连接地址,并构造出mybatis的sql执行器(SqlSession))
  3. 配置Start模块,让使用者自需要引用Starter依赖即可快速使用其核心功能。

接下来,我们将通过以上的三个核心流程来定义自己的Starter

一、构建核心业务代码模块

1、创建一个Maven项目 Pom文件如下所示

<?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>
	
    <!--  该构件的 GAV -->
    <groupId>com.wyx</groupId>
    <artifactId>my-core</artifactId>
    <version>1.0</version>

    <!--  打包时携带源码 -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <executions>
                    <execution>
                        <id>attach-sources</id>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

2、编写业务的核心处理程序(这里编写一个普通类即可,需要时根据业务需求自行更改)

package com.wyx.core;

/**
 * @author wangyuxing
 * @date 2022/8/16 17:22
 */
public class Core {

    public void getCore(){
        System.out.println("调用核心方法,处理程序!");
    }
}

就这样简单两步,我们的核心处理程序就构建完成

二、配置自动配置类

1、创建一个Maven项目,pom内容如下

<?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>

    <!-- 构建的GAV -->
    <groupId>com.wyx</groupId>
    <artifactId>my-spring-boot-autoconfigure</artifactId>
    <version>1.0</version>

    <!-- 该依赖作用是为后面的SpringBoot依赖统一版本(也可不使用,但推荐使用) -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.10</version>
    </parent>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!-- 第一步中自己构建的核心处理类 -->
        <dependency>
            <groupId>com.wyx</groupId>
            <artifactId>my-core</artifactId>
            <version>1.0</version>
        </dependency>

        <!-- springboot启动依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <!--  该依赖不会向下传递  -->
            <optional>true</optional>
        </dependency>

        <!-- springboot自动配置核心依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
            <!--  该依赖不会向下传递  -->
            <optional>true</optional>
        </dependency>

        <!-- springboot 配置文件处理器(导入该依赖输入配置文件时可提示) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <!--  该依赖不会向下传递  -->
            <optional>true</optional>
        </dependency>

        <!-- 这个依赖的实质作用是添加一些类,从而达到条件装配的作用  -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <!--  打包是生效,如果是通过类条件注入,那么该作用域必不可少  -->
            <scope>provided</scope>
            <!--  该依赖不会向下传递  -->
            <optional>true</optional>
        </dependency>
    </dependencies>

    <!-- 打包时携带源码 -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <executions>
                    <execution>
                        <id>attach-sources</id>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

2、编辑Bean实例

package com.wyx.spring.boot.bean;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;

/**
 * @author wangyuxing]
 * @date 2022/8/16 17:11
 */
@EnableConfigurationProperties
@ConfigurationProperties("com.hello")
public class Hello {

    private String name = "默认名";

    private String sex = "默认性别";

    private String age = "默认年龄";

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    /**
     * 方法
     */
    public String tellMe(){
        return this.toString();
    }

    @Override
    public String toString() {
        return "Hello{" +
                "name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", age='" + age + '\'' +
                '}';
    }
}

3、编辑核心的自动配置类

package com.wyx.spring.boot.autoconfigure;

import com.wyx.spring.boot.bean.Hello;
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;

/**
 * @author wangyuxing
 * @date 2022/8/16 16:45
 */
@Configuration
@EnableConfigurationProperties(Hello.class)  // Hello类中有ConfigurationProperties才生效
public class MyAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(Hello.class)  // 存在Hello类才注入容器
    public Hello hello(){
        return new Hello();
    }

}

package com.wyx.spring.boot.autoconfigure;

import io.netty.util.CharsetUtil;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.yaml.snakeyaml.util.UriEncoder;

/**
 * @author wangyuxing
 * @date 2022/8/16 16:55
 */
// 该类是一个条件注解类,用于展示条件的作用
@Configuration
@ConditionalOnClass(CharsetUtil.class)
public class OtherAutoConfiguration {

    @Configuration
    @ConditionalOnClass(UriEncoder.class)
    @ConditionalOnMissingClass("org.yaml.snakeyaml.util.UriEncoder")
    public static class LegacyFreeMarkerConfiguration {
        @Bean
        @ConditionalOnMissingBean
        UriEncoder defaultThreadFactory() {
            return null;
        }
    }
}

4、配置完bean实例以及自动配置类接下来也是是要使整个自动配置类生效的核心配置,在上面的讲解中自需要自定义一个spring.factories文件即可,即在resources目录下新建META-INF目录,在该目录下新增spring.factories文件,其配置内容如下

# 自动导入配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.wyx.spring.boot.autoconfigure.MyAutoConfiguration,\
com.wyx.spring.boot.autoconfigure.OtherAutoConfiguration
# springboot还有更多的注解,可根据实际情况参考

三、配置Starter模块

​ 根据上面的操作完成后配置Starter模块将会变得非常简单,只需要构建一个Maven项目并导入my-spring-boot-autoconfigure(上面定义的模块名)

pom的文件内容如下

<?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>

    <!-- Starter模块GAV -->
    <groupId>com.wyx</groupId>
    <artifactId>my-spring-boot-starter</artifactId>
    <version>1.0</version>

    <properties>
        <!-- 导出模块名称 -->
        <module.name>com.my.spring.boot.starter</module.name>
    </properties>

    <dependencies>
        <!-- 自动配置Starter 模块GAV -->
        <dependency>
            <groupId>com.wyx</groupId>
            <artifactId>my-spring-boot-autoconfigure</artifactId>
            <version>1.0</version>
        </dependency>
    </dependencies>
    <!--打包是附带源码打包-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <executions>
                    <execution>
                        <id>attach-sources</id>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

​ 到此自动配置功能全部完成,我们自需要按照maven的引入层级目录进行install操作即可。

自定义Starter测试使用

1、创建一个SpringBoot应用

2、导入自定义的starter,其导入完成的pom如下所示

<?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 https://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.6.10</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.wyx</groupId>
    <artifactId>my</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>my</name>
    <description>my</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--自定义的SpringBootStarter启动依赖-->
        <dependency>
            <groupId>com.wyx</groupId>
            <artifactId>my-spring-boot-starter</artifactId>
            <version>1.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

3、编写主启动类

package com.wyx;

import com.wyx.core.Core;
import com.wyx.spring.boot.bean.Hello;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }

    @RestController
    public class helloController {

        @Autowired
        Hello hello;

        @GetMapping("/")
        public String helloIKun(){

            Core core = new Core();
            core.getCore();

            return hello.tellMe();
        }
    }
}

访问对应的URL

4、配置application.yml文件

com:
  hello:
    name: "王玉霞"
    age: "12"
    sex: "男"

重新启动项目,再次访问对应的URL

posted @ 2022-08-29 11:03  橘子有点甜  阅读(414)  评论(0编辑  收藏  举报