springboot項目中spring factories特性原理剖析

平常項目開發中,相信很多小夥伴都有過這樣的經歷

  • 基礎架構組,或者平臺組的同事會給我們提供一些sdk,我們拿到這些sdk,只需要在pom.xml中添加依賴,就可以直接使用了

  • 使用springboot作爲基礎框架,需要某個組件,只需要在pom.xml,添加xxx-starter依賴,就可以直接使用了

  • 當需要更換sdk實現方式,xxx-starter組件的時候,只需要更換依賴即可,並不需要修改應用代碼

有沒有領會到以上描述的奧妙之處?即我們的應用代碼,與基礎組件sdk,xxx-starter之間是解耦的,後期代碼維護起來極其方便!

那麼這麼好的解決方案,究竟是如何實現的呢?這就是今天這篇文章我要分享給你的

1.java spi機制與入門案例

1.1.spi介紹

以上我們說的實現應用代碼,與基礎組件之間的解耦,其實我們是在描述一個事實

  • 在模塊化、組件化開發的實踐中,需要遵循高內聚、低耦合的思想

  • 其中實現解耦的一個基本思路是面向接口編程,即接口隔離的設計原則

  • 模塊與模塊之間,組件之間依賴接口,而非具體的實現,即最大化的實現瞭解耦

這是我們在項目中經常推薦的做法,除了提倡面向接口編程這個事實,我們還需要考慮另外一個事實

  • 定義好了接口,模塊之間通過依賴接口,實現解耦

  • 但是一個接口,可能會有多中實現方案,實現方案A、實現方案B...

  • 應用程序在執行的時候,如何知道具體實現的是方案A,還是方案B呢

這就是我要描述的另外一個事實,我們還需要一個發現接口實現的機制它也就是spi機制

總結一下,spi的全稱是service provider interface,它是java提供的規範,該規範

  • 強調基於接口編程,實現模塊之間的解耦

  • 具體實現,首先要定義接口規範

  • 然後每個服務提供者,參照規範約定提供接口的具體實現

  • 使用者,通過ServiceLoader工具,即可獲取服務提供者的具體實現

  • 舉個例子,想想jdbc編程實踐中,不同數據庫廠商提供的驅動實現,比如mysql、oracle

  • 再舉個例子,插件化編程的時代,各種插件的使用

以上關於spi的介紹,如果你是第一次關注的話,相信此時一定是蒙圈的,不過不要緊,繼續看完後面的案例, 再回過頭來看 你就能明白了。

1.2.入門案例

1.2.1.代碼接口

通過一個基礎案例,演示java spi的基本使用,案例結構如下

這是一個maven項目,由三個類文件,以及一個配置文件組成

  • Animal:接口規範,spi強調基於接口編程

  • Bird:Animal接口的實現

  • SpiTest:使用者,通過ServiceLoader工具,獲取Animal接口的具體實現

  • META-INF/services/cn.edu.spi.Animal:spi規範的約定,即接口與接口實現的描述文件

1.2.2.代碼

Animal接口

/**
 * spi 案例:接口
 *
 * @author ThinkPad
 * @version 1.0
 * @date 2021/9/4 15:10
 */
public interface Animal {

    /**
     * 動物喊話
     * @param name
     */
    void hello(String name);
}

Bird實現類

/**
 * spi案例:接口實現 鳥
 *
 * @author ThinkPad
 * @version 1.0
 * @date 2021/9/4 15:12
 */
public class Bird  implements Animal{

    /**
     * 動物喊話
     *
     * @param name
     */
    @Override
    public void hello(String name) {
        System.out.println("I am small small small Bird! name is:" +name);
    }
}

META-INF/services/cn.edu.spi.Animal配置文件

cn.edu.spi.impl.Bird

SpiTest類

/**
 * 測試類
 *
 * @author ThinkPad
 * @version 1.0
 * @date 2021/9/4 15:15
 */
public class SpiTest {

    public static void main(String[] args) {
        ServiceLoader<Animal> animals = ServiceLoader.load(Animal.class);
        Iterator<Animal> iter = animals.iterator();
        while (iter.hasNext()){
            Animal animal = iter.next();
            animal.hello("Bob");
        }
    }
}

#運行結果
I am small small small Bird! name is:Bob

Process finished with exit code 0

2.java spi綜合案例

有了spi基礎案例,接下來我們做一個更加接近項目實踐的案例。我們來準備以下一些事情

  • 構建sdk接口規範工程:follow-me-spi-animal,它有兩個實現

  • 構建sdk接口規範實現工程cat:follow-me-spi-cat,提供具體實現方案1

  • 構建sdk接口規範實現工廠dog:follow-me-spi-dog,提供具體實現方案2

  • 構建使用者工程:follow-me-springboot-api,實現在cat、dog之間隨意切換實現方式,而不需要修改應用代碼

2.1.工程:follow-me-spi-animal

2.1.1.pom.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>

    <!--spi 接口gav信息-->
    <groupId>cn.edu.anan</groupId>
    <artifactId>follow-me-spi-animal</artifactId>
    <version>0.0.1</version>

    
</project>

2.1.2.接口

package cn.edu.anan;

/**
 * spi接口
 *
 * @author ThinkPad
 * @version 1.0
 * @date 2021/9/4 16:09
 */
public interface Animal {
    /**
     * 接口方法
     */
    void hello();
}

2.2.工程:follow-me-spi-cat

2.2.1.pom.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>

    <!--項目 gav信息-->
    <groupId>cn.edu.anan</groupId>
    <artifactId>follow-me-spi-cat</artifactId>
    <version>0.0.1</version>

    <!--公共變量定義-->
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <!--spi interface依賴-->
        <dependency>
            <groupId>cn.edu.anan</groupId>
            <artifactId>follow-me-spi-animal</artifactId>
            <version>0.0.1</version>
        </dependency>
    </dependencies>

    <!--項目構建插件-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    
</project>

2.2.2.接口實現

package cn.edu.anan;

/**
 * pi Animal接口實現2
 *
 * @author ThinkPad
 * @version 1.0
 * @date 2021/9/4 16:22
 */
public class Cat implements Animal{

    /**
     * 接口方法
     */
    @Override
    public void hello() {
        System.out.println("I am Cat.喵喵...");
    }
}

2.2.3.spi配置

META-INF/services/cn.edu.anan.Animal

cn.edu.anan.Cat

2.3.工程:follow-me-spi-dog

2.3.1.pom.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>

    <!--項目 gav信息-->
    <groupId>cn.edu.anan</groupId>
    <artifactId>follow-me-spi-dog</artifactId>
    <version>0.0.1</version>

    <!--公共變量定義-->
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <!--spi interface依賴-->
        <dependency>
            <groupId>cn.edu.anan</groupId>
            <artifactId>follow-me-spi-animal</artifactId>
            <version>0.0.1</version>
        </dependency>
    </dependencies>

    <!--項目構建插件-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    
</project>

2.3.2.接口實現

package cn.edu.anan;

/**
 * spi Animal接口實現1
 *
 * @author ThinkPad
 * @version 1.0
 * @date 2021/9/4 16:13
 */
public class Dog implements Animal{

    /**
     * 接口方法
     */
    @Override
    public void hello() {
        System.out.println("I am Dog.汪汪...");
    }
}

2.3.3.spi配置

META-INF/services/cn.edu.anan.Animal

cn.edu.anan.Dog

2.4.工程:follow-me-springboot-api

2.4.1.依賴follow-me-spi-cat

導入依賴

<!--follow-me-spi-cat依賴-->
<dependency>
    <groupId>cn.edu.anan</groupId>
    <artifactId>follow-me-spi-cat</artifactId>
    <version>0.0.1</version>
</dependency>

應用配置

/**
 * spi 配置類
 *
 * @author ThinkPad
 * @version 1.0
 * @date 2021/9/4 15:59
 */
@Configuration
public class SpiConfiguration {

    @Bean
    public Animal animal(){
        Animal animal = null;

        ServiceLoader<Animal> animals = ServiceLoader.load(Animal.class);
        Iterator<Animal> iter = animals.iterator();
        while (iter.hasNext()){
            animal = iter.next();
            break;
        }

        return animal;
    }
}

controller

/**
 * spi controller
 *
 * @author ThinkPad
 * @version 1.0
 * @date 2021/9/4 15:55
 */
@RestController
@RequestMapping("spi")
@Slf4j
public class SpiController {

    @Autowired
    private Animal animal;

    @RequestMapping("test")
    public String test(){
        animal.hello();
        return animal.getClass().getName();
    }
}

啓動應用,訪問端點:http://127.0.0.1:8080/spi/test

響應:cn.edu.anan.Cat

控制檯輸出:I am Cat.喵喵...

2.4.2.依賴follow-me-spi-dog

將依賴換成dog

 

 <!--follow-me-spi-dog依賴-->
<dependency>
    <groupId>cn.edu.anan</groupId>
    <artifactId>follow-me-spi-dog</artifactId>
    <version>0.0.1</version>
</dependency>

<!--follow-me-spi-cat依賴-->
<!--<dependency>
<groupId>cn.edu.anan</groupId>
	<artifactId>follow-me-spi-cat</artifactId>
    <version>0.0.1</version>
</dependency>-->

啓動應用,訪問端點:http://127.0.0.1:8080/spi/test

響應:cn.edu.anan.Dog

控制檯輸出:I am Dog.汪汪...

3.springboot中的spi機制

在springboot應用中,經常會用到spi機制,我們稱之爲spring factories機制,它的實現原理與java spi機制一致,它提供了

  • SpringFactoriesLoader工具:作用等價於ServiceLoader,用於發現加載接口實現,即服務提供者實現方案

  • META-INF/spring.factories:它約定將spi服務實現,配置到META-INF/spring.factories文件中,以kev/value的形式存在

舉幾個例子,比如說spring-boot

比如說,spring-boot-autoconfigure

具體spring.factories文件內容都比較多,我就不復制粘貼了,我建議你一定要自己打開看一看。另外我寫了一個簡單的參考案例:follow-me-springboot-thirtybean

本文相關源碼,倉庫地址:https://gitee.com/yanghouhua/springboot.git

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章