Java SPI 机制

 2023-02-06
原文作者:蒋先森 原文地址:https://jlj98.top/

今天在看dubbo源码的时候,看到大量的SPI,对于SPI不是很明白,于是网上看资料和例子,有了这篇文章。

SPI(Service Provider Interface),是Java提供的一套用来被第三方实现或者扩展的API,可以用来启动框架扩展和替换组件。

202212301147322001.png

使用场景

  • 数据库驱动加载
  • dubbo
  • 日志门面模式实现不同日志

SPI 的使用

定义接口并实现接口

    public interface Spi {
    
        /**
         * spi接口
         * */
        void sayHello();
    }
    public class Cat implements Spi {
    
        @Override
        public void sayHello() {
            System.out.println("Hello World! This is a Cat");
        }
    }
    
    public class Dog implements Spi {
    
        @Override
        public void sayHello() {
            System.out.println("Hello World! This is a Dog");
        }
    }

src/main/resources/ 创建文件

在src/main/resources/ 目录下创建 /META-INF/services文件(关于services文件夹,如果使用Java自带的需要使用这个名字,如果自己实现可以自定义),并在文件夹中 创建与接口同名的文件 ——com.example.spi.Spi

202212301147333372.png

测试

    public static void main(String[] args) {
        ServiceLoader<Spi> services = ServiceLoader.load(Spi.class);
        Iterator<Spi> iterator = services.iterator();
        while (iterator.hasNext()) {
            iterator.next().sayHello();
        }
    }

输出:

    Hello World! This is a Cat
    Hello World! This is a Dog

SPI 原理解析

    public final class ServiceLoader<S> implements Iterable<S>{
        //被加载的类或接口
        private final Class<S> service;
        
        private final String serviceName;
    
        private final ModuleLayer layer;
    
        // 用于定位、加载和初始化providers的加载类
        private final ClassLoader loader;
    
        // 创建ServiceLoader是用于上下文切换
        private final AccessControlContext acc;
    
        //缓存providers,按实例化的顺序排列
        private Iterator<Provider<S>> lookupIterator1;
        private final List<S> instantiatedProviders = new ArrayList<>();
    
        // The lazy-lookup iterator for stream operations
        private Iterator<Provider<S>> lookupIterator2;
        private final List<Provider<S>> loadedProviders = new ArrayList<>();
        private boolean loadedAllProviders;
    }

流程

  • 用ServiceLoader.load方法ServiceLoader.load方法内先创建一个新的ServiceLoader,并实例化该类中的成员变量,包括:

    • loader(ClassLoader类型,类加载器)
    • acc(AccessControlContext类型,访问控制器)
    • providers(LinkedHashMap<String,S>类型,用于缓存加载成功的类)
    • lookupIterator(实现迭代器功能)
  • 应用程序通过迭代器接口获取对象实例

  • 读取配置文件

  • 通过反射Class.forName()加载类对象,并初始化

  • 把实例化后的类缓存到providers对象中,然后返回实例对象

源码