由于blog各种垃圾评论太多,而且本人审核评论周期较长,所以懒得管理评论了,就把评论功能关闭,有问题可以直接qq骚扰我

JAVA—SPI机制

JAVA 西门飞冰 1006℃
[隐藏]

1.SPI 介绍

在进行应用程序开发的时候,经常有这样一类需求,在不修改源代码的情况下,动态的为我们的程序提供一系列特性,比如我们可以为每一个方法在执行前,执行后动态的运算它的执行时间;或者收集jvm、内存、cpu的运行指标等等,像这些灵活扩展的功能该如何实现呢?在springboot的框架里有aop技术,也就是面向切面编程来构建我们的应用插件,这是其中的一种方式

在java自带的领域就有着完整类似的实现,可以通过语言方面的机制,帮助我们动态的进行能力上的扩展,这就是SPI机制。

对于SPI来说,它不是一个复杂的技术,它是一套开发的规范,只要符合了这个规范,就可以动态的为程序进行插件的扩展和功能的增强了。

2.案例

我希望在⾃⼰的系统中可以增加灵活的插件机制,并且不需要修改源代码,只需要通过往程序中增加一个一个编译好的插件jar包,就可以为应⽤程序灵活扩展新的功能。

下面就来演示一下,基于JAVA SPI 机制来实现灵活扩展,灵活插拔的应用插件是如何实现的

2.1.应用程序项目SPI-APP

定义SPI接口

import java.util.Map;

// SPI 接口,所有的动态扩展都是基于这个接口来的
public interface Plugin {
    public boolean install(Map context);
}

编写插件的加载安装工厂:

// 插件工厂,作用,加载目前所有可以被使用的plugin实现类
// 在我们的工程中并没有plugin实现类,但是这并不影响动态加载的过程
public class PluginFactory {
    public void installPlugins(){
        Map context = new LinkedHashMap();
        //模拟插件安装或运⾏时所需要的各类应⽤本体信息
        //当前IOC容器中的对象集合
        context.put("_beans", new ArrayList<>());
        context.put("_version", "1.0.0");
        //切⾯类
        context.put("_aspects", new LinkedHashMap<>());
        // 扫描classpath下所有Plugin的实现类
        ServiceLoader<Plugin> loader = ServiceLoader.load(Plugin.class);
        Iterator<Plugin> iterator = loader.iterator();
        while(iterator.hasNext()){
            Plugin plugin = iterator.next();
            plugin.install(context);
        }
    }

    public static void main(String[] args) {
        new PluginFactory().installPlugins();		// 这个时候运行是没有任何插件的
    }
}

开发插件之前,因为所有的插件都要实现Plugin接口,所以要将当前这个工程在本地的maven仓库中进行安装,这样在其他的子工程下就可以进行引用了。

增加context上下文的目的:

插件安装过程中,要获取应用程序本身的一些信息,如beans、切面类,或者通过上下文给它开放一些可以给灵活注入的接口,这些我们都给它包含在了Map上下文对象中

这个Map上下文对象其实说白了就是我们应用程序当前运行本身的环境参数,只有这些环境参数存在的情况下,我们的插件才可以感知到应用程序本身,并进行相应的安装和程序的处理。

ServiceLoader服务加载者:

进行实现类的扫描,扫描到的每一个对象,都被包装到了loader对象里面,使用的时候使用迭代器对它进行遍历,拿到具体的实现类之后,通过plugin.install 安装就可以了,安装过程中将上下文对象进行传入。

2.2.日志插件项目SPI-Log-Plugin

增加一个额外的spi-app的依赖,目的是实现相同的Plugin接口,在相同的Plugin接口下增加相应的实现

<dependency>
    <groupId>com.fblinux</groupId>
    <artifactId>SPI-APP</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

实现接口:在install方法中实现对插件逻辑上的安装

import java.util.Map;

public class LogPlugin implements Plugin{
    @Override
    public boolean install(Map context) {
        System.out.println("LogPlugin已初始化,logger对象已注⼊IOC容器");
        return true;
    }
}

此时我们实现了Plugin接口以及install方法,那应该如何当应用程序动态的发现这个插件呢?

这里有一个关键的设计,在resources目录下,要创建一个两级目录META-INF/services⽬录,新建 com.fblinux.spi.Plugin⽂件(和接口全名对应,⽆扩展名)

我们要在这个文件里说明的是,具体的实现类,这里对于加载的数量来说是没有上限的,我们每做一个jar包,只要是按照这个规则来写的话,都可以进行动态加载和扩展

image-20230116115457433

然后,将log-plugin插件打包成Jar包,放在应用程序项目app的classpath下就行,当app项目启动时调用

在SPI-APP目录下,引入SPI-Log-Plugin依赖

<dependency>
    <groupId>com.fblinux</groupId>
    <artifactId>SPI-Log-Plugin</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

然后SPI-APP项目就会根据,new PluginFactory().installPlugins() 代码自动完成插件的注入

image-20230116120102511

程序输出结果

image-20230116115854484

3.常用加载plugin途径

maven依赖

应用程序的lib⽬录

应用程序指定的classpath⽬录

 

转载请注明:西门飞冰的博客 » JAVA—SPI机制

喜欢 (1)or分享 (0)