SPI实现项目可插拔式使用项目额外组件实践

        最近有个项目需要存储文件内容,客户又不使用产品默认对象存储,产品又不想将定制的代码融入到产品中,因此需要设计成可插拔的插件,初次接触到SPI 和 SpringBoot Starter。两者很像,又有所区别,这里梳理一下SPI实现;下面是一个完整的示例;

        网上很多博客所有代码都揉在一个工程中,不便于理解和使用,这里按真正的使用场景分为了3个工程;示例需要3个jar ; 接口jar 、插件jar、 业务jar;文章最后有源码链接。

SPI实现项目可插拔式使用项目额外组件实践

IDEA工程如下: 

SPI实现项目可插拔式使用项目额外组件实践

1.编写接口jar--供另外两个jar使用

 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>

    <groupId>com.idto.spi</groupId>
    <artifactId>idto-spi-api</artifactId>
    <version>1.0</version>
    <description>SPI-接口层jar包</description>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>7</source>
                    <target>7</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>

    </dependencies>
</project>

定义一个接口类Say

package com.idto.spi;

/**
 * @author idto
 * @title: Say
 * @projectName
 * @description: SPI定义的接口 需要业务层和实现的插件引用
 * @date 2022/2/22 10:40
 */
public interface Say {

    void sayWithLanguage(String language);
}

将idto-spi-api.jar编译打包到本地仓库中,稍后使用。

2.编写SPI插件--插件jar

 pom坐标,引入第1步打好的idto-spi-api.jar包

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

    <groupId>cn.org.bjca</groupId>
    <artifactId>idto-spi-plug</artifactId>
    <version>1.0</version>
    <description>SPI-插件jar包</description>

    <dependencies>
        <!--引入定义的接口-->
        <dependency>
            <groupId>com.idto.spi</groupId>
            <artifactId>idto-spi-api</artifactId>
            <version>1.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>7</source>
                    <target>7</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

编写接口Say的实现类,这里编写了两个实现类

package com.idto.spi.impl;

import com.idto.spi.Say;


public class America implements Say {

    @Override
    public void sayWithLanguage(String s) {
        System.out.println("Speak English -->"+s);
    }
}


package com.idto.spi.impl;

import com.idto.spi.Say;


public class China implements Say {
    @Override
    public void sayWithLanguage(String s) {
        System.out.println("中文说-->"+s);
    }
}

划重点  

在resources下新建META-INF.services ,然后在services下新建文件com.idto.spi.Say(接口的全路径),com.idto.spi.Say在文件中写入要用到的实现类的全路径,多个实现类分行写入

SPI实现项目可插拔式使用项目额外组件实践

 将idto-spi-plug.jar编译打包到本地仓库中,稍后使用。

到这不SPI实现已经完成,下面我们看看怎么使用吧!

 3.编写业务代码--业务jar

 pom坐标,引入第1步打好的idto-spi-api.jar包

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

    <groupId>cn.org.bjca</groupId>
    <artifactId>idto-spi-business</artifactId>
    <version>1.0</version>
    <description>SPI-业务jar包</description>

    <dependencies>
        <!--引入定义的接口-->
        <dependency>
            <groupId>com.idto.spi</groupId>
            <artifactId>idto-spi-api</artifactId>
            <version>1.0</version>
        </dependency>
    </dependencies>

    <build>
        <resources>
            <resource>
                <directory>src/main/resource</directory>
                <includes>
                    <include>*</include>
                </includes>
            </resource>
        </resources>

        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.1.1</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <!-- 设置主类  -->
                                    <mainClass>com.idto.spi.Main</mainClass>
                                </transformer>
                            </transformers>
                            <filters>
                                <filter>
                                    <artifact>*:*</artifact>
                                    <!-- 打包时过滤无用的文件,不打包进jar -->
                                    <excludes>
                                        <exclude>META-INF/*.SF</exclude>
                                        <exclude>META-INF/*.DSA</exclude>
                                        <exclude>META-INF/*.RSA</exclude>
                                    </excludes>
                                </filter>
                                <filter>
                                    <artifact>junit:junit</artifact>
                                    <includes>
                                        <include>junit/framework/**</include>
                                        <include>org/junit/**</include>
                                    </includes>
                                    <excludes>
                                        <exclude>org/junit/experimental/**</exclude>
                                        <exclude>org/junit/runners/**</exclude>
                                    </excludes>
                                </filter>
                            </filters>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

编写SPI的调用方法,通过java自带的ServiceLoader加载插件;代码中指定插件的目录,最好在项目jar外,方便插拔,代码中写死了插件的名称,仅供参考

package com.idto.spi;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ServiceLoader;


/**
 * @author idto
 * @title: Main
 * @projectName idto-spi-business
 * @description:  SPI调用例子
 * @date 2022/2/22 13:02
 */
public class Main {
    public static void main(String[] args) throws MalformedURLException {
        // 获取插件的路径
        String projectPath = System.getProperty("user.dir");
        Path path = Paths.get(projectPath);
        String plugPath = path.toString() + File.separator + "plugs";
        String location = plugPath + File.separator + "idto-spi-plug-1.0.jar";
        System.out.println("plug Path is -->" + location);
        System.out.println( );
        System.out.println( );

        System.out.println("---start---");
        URL url = new File(location).toURI().toURL();
        URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{url}, ClassLoader.getSystemClassLoader());
        ServiceLoader<Say> says = ServiceLoader.load(Say.class, urlClassLoader);
        for (Say say : says) {
            say.sayWithLanguage("Hello World!");
        }
        System.out.println("---end---");
    }
}

到这步,可以直接在代码进行测试了;

为了模拟真是使用场景,我们打包编译,将打好的idto-spi-business-1.0.jar 复制到一个测试目录下进行测试;

测试场景一:不放插件

SPI实现项目可插拔式使用项目额外组件实践

 没有找到对应的实现类,打印为空

测试场景一:按照指定目录安放插件

在D:\log\plugs下放入idto-spi-plug-1.0.jar

SPI实现项目可插拔式使用项目额外组件实践

 运行业务类代码,结果两个实现类的方法成功运行,如下:

SPI实现项目可插拔式使用项目额外组件实践

 

原创不易,如对您有所帮助,欢迎您点赞 收藏(#^.^#)

上一篇:【ubuntu 20.04】安装部署内网镜像服务器Harbor-1.10.7


下一篇:1249. Minimum Remove to Make Valid Parentheses