JDK和Spring的SPI机制原理分析

SPI机制

为什么需要SPI机制

在面向对象编程中,基于开闭原则和解耦的需要,一般建议用接口进行模块之间通信编程,通常情况下调用方模块是不会感知到被调用方模块的内部具体实现

为了实现在模块装配的时候不用在程序里面动态指明,这就需要一种服务发现机制。Java SPI 就是提供了这样一个机制:为某个接口寻找服务实现的机制。这有点类似IOC的思想,将装配的控制权移交到了程序之外。

什么是SPI机制

Java SPI(Service Provider Interface)是 Java 提供的一种轻量级的服务发现机制。它可以让开发者通过约定的方式,在程序运行时动态地加载和替换接口的实现,从而提高程序的扩展性和灵活性

Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制
JDK和Spring的SPI机制原理分析_第1张图片

要使用Java SPI,需要遵循如下约定:

  1. 当服务提供者提供了接口的一种具体实现后,在jar包的META-INF/services目录下创建一个以接口全限定名为命名的文件,内容为实现类全限定名

  2. 接口实现类所在的jar包放在主程序的classpath

  3. 主程序通过java.util.ServiceLoder动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM

  4. SPI的实现类必须携带一个无参构造方法

JDK SPI机制

搭建一下场景所需要的环境

现有一个日志框架Logger, 下有一个Logger接口, 以及两个实现类

package com.whitebrocade;

/**
 * @author whitebrocade
 */
public interface Logger {
    void log(String message);
}


package com.whitebrocade;

/**
 * @author whitebrocade
 */
public class ConsoleLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println("模拟log输出到控制台:" + message);
    }
}


package com.whitebrocade;

/**
 * @author whitebrocade
 */
public class FileLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println("模拟log写入文件:" + message);
    }
}

需求: 上述两个Logger实现类不足以满足我们的需求, 想自定义拓展

步骤如下

  1. 新建自己的类MyLogger以及CustomLogger实现Logger接口

    package com.whitebrocade;
    
    /**
     * @author whitebrocade
     */
    public class MyLogger implements Logger {
        @Override
        public void log(String message) {
            System.out.println("自定义log实现:" + message);
        }
    }
    
    
    package com.whitebrocade;
    
    /**
     * @author whitebrocade
     */
    public class CustomLogger implements Logger {
        @Override
        public void log(String message) {
            System.out.println("自定义log实现2:" + message);
        }
    }
    
  2. resource下新建META-INF/services, 注意了两个文件夹的名字一定不能变, 规定死的, 约定大于配置(后续会说明)

  3. 点击MyLogger, Ctrl + Shiht + Alt + C复制Logger的全限定类名

    • 例如我是是com.whitebrocade.Logger, 注意了我复制的是接口的全类名
  4. 在service新建文件, 文件名为就是刚才复制的全类名(是的, 你没看错, 文件名就是这么奇怪)

  5. 在刚才的新建的文件里, 再次将MyLogger和CustomLoggerd的全类名复制下去()

    • 以我的为例子, resource/META-INF/services/com.whitebrocade.Logger文件中的内容如下, 注意一个全类名一行, 如果后续还有其他的类需要加载, 那么继续按照一下格式添加即可

    • com.whitebrocade.MyLogger
      com.whitebrocade.CustomLogger
      
  6. 新建一个test类用于测试

    public class test {
        public static void main(String[] args) {
            ServiceLoader<Logger> loggerServiceLoader = ServiceLoader.load(Logger.class);
            for (Logger logger : loggerServiceLoader) {
                logger.log("Hello, Java SPI!");
            }
        }
    }
    // 输出结果如下, 说明确实读取到了
    自定义log实现:Hello, Java SPI!
    自定义log实现2:Hello, Java SPI!
    

load方法是通过获取当前线程的 线程上下文类加载器 实例来加载的。Java应用运行的初始线程的上下文类加载器默认是系统类加载器。这里其实 破坏了双亲委派模型,因为Java应用收到类加载的请求时,按照双亲委派模型会向上请求父类加载器完成,这里并没有这么做

上述代码的关系如下
JDK和Spring的SPI机制原理分析_第2张图片

JDK SPI机制原理解析

下述的分析基于JDK 11的SPI

// ServiceLoader实现了Iterable接口,可以遍历所有的服务实现者
public final class ServiceLoader<S> implements Iterable<S> {
    // 表示要被加载的服务的类或接口
    private final Class<S> service;
    // 这个ClassLoader用来定位,加载,实例化服务提供者
    private final ClassLoader loader;
    // 访问控制上下文
    private final AccessControlContext acc;
    
   // 用于迭代器操作的lazy-lookup迭代器
    private Iterator<Provider<S>> lookupIterator1;
    private final List<S> instantiatedProviders = new ArrayList<>();
}
iterator()
  1. newLookupIterator(): 初始迭代器
  2. hasNext(): 判断是否有下一个元素
  3. next(): 获取下一个元素
public Iterator<S> iterator() {
    // 首次进来初始化
    if (lookupIterator1 == null) {
        lookupIterator1 = newLookupIterator();
    }

    return new Iterator<S>() {

        // record reload count
        final int expectedReloadCount = ServiceLoader.this.reloadCount;

        // index into the cached providers list
        int index;

        /**
        * Throws ConcurrentModificationException if the list of cached
        * providers has been cleared by reload.
        */
        private void checkReloadCount() {
            if (ServiceLoader.this.reloadCount != expectedReloadCount)
                throw new ConcurrentModificationException();
        }

        @Override
        public boolean hasNext() {
            checkReloadCount();
            if (index < instantiatedProviders.size())
                return true;
            // 调用的是java.util.Iterator#hasNext
            return lookupIterator1.hasNext();
        }

        @Override
        public S next() {
            checkReloadCount();
            S next;
            if (index < instantiatedProviders.size()) {
                next = instantiatedProviders.get(index);
            } else {
                next = lookupIterator1.next().get();
                instantiatedProviders.add(next);
            }
            index++;
            return next;
        }

    };
}

private Iterator<Provider<S>> newLookupIterator() {
    assert layer == null || loader == null;
    if (layer != null) {
        return new LayerLookupIterator<>();
    } else {
        Iterator<Provider<S>> first = new ModuleServicesLookupIterator<>();
        Iterator<Provider<S>> second = new LazyClassPathLookupIterator<>();
        return new Iterator<Provider<S>>() {
            @Override
            public boolean hasNext() {
                // second.hasNext()调用的是java.util.ServiceLoader.LazyClassPathLookupIterator#hasNext
                return (first.hasNext() || second.hasNext());
            }
            @Override
            public Provider<S> next() {
                if (first.hasNext()) {
                    return first.next();
                } else if (second.hasNext()) {
                    // 进入java.util.ServiceLoader.LazyClassPathLookupIterator#next
                    return second.next();
                } else {
                    throw new NoSuchElementException();
                }
            }
        };
    }
}
nextProviderClass()

这个方法说明了

  1. 为什么一定要放在META-INF/services/
  2. 为什么要文件中要填写全类名
// java.util.ServiceLoader.LazyClassPathLookupIterator#nextProviderClass
private final class LazyClassPathLookupIterator<T>
    implements Iterator<Provider<T>>
{
    // 查找配置文件的目录
    // PREFIX说明了为什么只能放在META-INF/services/目录
    static final String PREFIX = "META-INF/services/";
    // 省略其他代码...
    
    private Class<?> nextProviderClass() {
        // config初始化
        if (configs == null) {
            try {
                // 路径全名称为:  "META-INF/services/" + 类的名称
                // service.getName()说明了为什么一定要全路径命名
                String fullName = PREFIX + service.getName();
                // loader == null, 说明是bootstrap类加载器
                if (loader == null) {
                    // 从classpath中加载指定的文件
                    configs = ClassLoader.getSystemResources(fullName);
                } else if (loader == ClassLoaders.platformClassLoader()) {
                    if (BootLoader.hasClassPath()) {
                        configs = BootLoader.findResources(fullName);
                    } else {
                        configs = Collections.emptyEnumeration();
                    }
                } else {
                    // 通过名字加载所有文件资源
                    configs = loader.getResources(fullName);
                }
            } catch (IOException x) {
                fail(service, "Error locating configuration files", x);
            }
        }

        // 遍历所有的资源,pending用于存放加载到的实现类
        while ((pending == null) || !pending.hasNext()) {
            if (!configs.hasMoreElements()) {
                // 遍历完所有的文件了,直接返回
                return null;
            }
            // parse方法主要调用了parseLine,功能:
            // 1. 分析每个PREFIX + service.getName() 目录下面的所有文件
            // 2. 判断每个文件是否是合法的java类的全限定名称,如果是就add到pending变量中
            pending = parse(configs.nextElement());
        }
        // 除了第一次进来,后面每次调用都是直接到这一步了
        // cn即className-全类名
        String cn = pending.next();
        try {
            // 通过Class.forName返回配置文件的中的Class对象
            return Class.forName(cn, false, loader);
        } catch (ClassNotFoundException x) {
            fail(service, "Provider " + cn + " not found");
            return null;
        }
    }
    
    // 省略其他代码...
}
hasNextService
// java.util.ServiceLoader.LazyClassPathLookupIterator#hasNextService
private final class LazyClassPathLookupIterator<T>
    implements Iterator<Provider<T>>
{
    // 省略其它代码...
    
    @SuppressWarnings("unchecked")
    private boolean hasNextService() {
        while (nextProvider == null && nextError == null) {
            try {
                // 调用nextProviderClass()获取Class对象
                Class<?> clazz = nextProviderClass();
                if (clazz == null)
                    return false;

                if (clazz.getModule().isNamed()) {
                    // ignore class if in named module
                    continue;
                }

                // 判断clazz对象是不是service的实现类或者子类
                // 如果是, 那么就调用实现类初始化
                // 以我们程序为例子
                // clazz就是class com.whitebrocade.MyLogger
                // type就是class com.whitebrocade.Logger
                // cotor就是public com.whitebrocade.MyLogger()
                if (service.isAssignableFrom(clazz)) {
                    // 通过无参构造器初始化clazz
                    Class<? extends S> type = (Class<? extends S>) clazz;
                    Constructor<? extends S> ctor
                        = (Constructor<? extends S>)getConstructor(clazz);
                    ProviderImpl<S> p = new ProviderImpl<S>(service, type, ctor, acc);
                    // 将实现类赋值给nextProvider
                    // 即Logger logger = new MyLogger()意思
                    nextProvider = (ProviderImpl<T>) p;
                } else {
                    fail(service, clazz.getName() + " not a subtype");
                }
            } catch (ServiceConfigurationError e) {
                nextError = e;
            }
        }
        return true;
    }
    
    // 省略其它代码...
}
nextService
// java.util.ServiceLoader.LazyClassPathLookupIterator#nextService
private final class LazyClassPathLookupIterator<T>
    implements Iterator<Provider<T>> {
    // 省略其它代码...
    
    private Provider<T> nextService() {
        // 校验一下
        if (!hasNextService())
            throw new NoSuchElementException();

        // 
        Provider<T> provider = nextProvider;
        if (provider != null) {
            nextProvider = null;
            // 将provider返回, provider的赋值动作在hasNextService()
            return provider;
        } else {
            ServiceConfigurationError e = nextError;
            assert e != null;
            nextError = null;
            throw e;
        }
    }
    
    // 省略其它代码...
}
next()
// java.util.ServiceLoader.LazyClassPathLookupIterator#next
private final class LazyClassPathLookupIterator<T>
    implements Iterator<Provider<T>> {
    // 省略其它代码...
    
    @Override
    public Provider<T> next() {
        if (acc == null) {
            return nextService();
        } else {
            PrivilegedAction<Provider<T>> action = new PrivilegedAction<>() {
                public Provider<T> run() { return nextService(); }
            };
            return AccessController.doPrivileged(action, acc);
        }
    }
    
    // 省略其它代码...
}
iterator()
public Iterator<S> iterator() {

    // create lookup iterator if needed
    if (lookupIterator1 == null) {
        lookupIterator1 = newLookupIterator();
    }

    return new Iterator<S>() {

        // record reload count
        final int expectedReloadCount = ServiceLoader.this.reloadCount;

        // index into the cached providers list
        int index;

        /**
             * Throws ConcurrentModificationException if the list of cached
             * providers has been cleared by reload.
             */
        private void checkReloadCount() {
            if (ServiceLoader.this.reloadCount != expectedReloadCount)
                throw new ConcurrentModificationException();
        }

        @Override
        public boolean hasNext() {
            checkReloadCount();
            if (index < instantiatedProviders.size())
                return true;
            return lookupIterator1.hasNext();
        }

        @Override
        public S next() {
            checkReloadCount();
            S next;
            if (index < instantiatedProviders.size()) {
                next = instantiatedProviders.get(index);
            } else {
                next = lookupIterator1.next().get();
                instantiatedProviders.add(next);
            }
            index++;
            return next;
        }

    };
}
流程图

文字描述

当首次使用迭代

  1. 调用iterator()类中newLookupIterator()初始化迭代器, 并返回迭代器

后续流程

  1. 调用iterator()类中hasNext()判断是否有下一个实现类
    • java.util.Iterator#hasNext
    • java.util.ServiceLoader.LazyClassPathLookupIterator#hasNext
    • java.util.ServiceLoader.LazyClassPathLookupIterator#hasNextService
    • nextProvider == null && nextError == null, nextProvider和nextError均为null则表示没有被赋值或者已经处理完上一个服务提供者且没有遇到错误, 进入whihe循环
      • java.util.ServiceLoader.LazyClassPathLookupIterator#nextProviderClass
      • 初始化nextProvider
    • 返回true表示有下一个元素
  2. 如果有, 那么就调用iterator()类中next()获取下一个实现类
    • java.util.Iterator#next
    • newLookupIterator中java.util.Iterator#next ->
      • second.hasNext()判断是否有下一个实现类
        • java.util.ServiceLoader.LazyClassPathLookupIterator#hasNext
        • java.util.ServiceLoader.LazyClassPathLookupIterator#hasNextService
        • nextProvider == null && nextError == null, 因为nextProvider不为null, 所以不进入while循环
        • 返回的是ture
      • 因为second.hasNext()返回的是true, 所以会指定second.next()获取下一个实现类:
        • java.util.ServiceLoader.LazyClassPathLookupIterator#next
        • java.util.ServiceLoader.LazyClassPathLookupIterator#nextService
          • 调用java.util.ServiceLoader.LazyClassPathLookupIterator#hasNextService判断是否下一个服务
            • nextProvider == null && nextError == null的条件中, 由于nextProvider不为null, 所以不会进入while循环
            • 返回true
        • 由于hasNextService()返回的是true, 所以!hasNextService()的值为false, 不会进入抛异常
          • nextProvider赋值给provider并返回provider

Spring SPI机制

Spring SPI对 Java SPI 进行了封装增强。我们只需要在resource目录下META-INF/spring.factories中配置接口实现类名,即可通过服务发现机制,在运行时加载接口的实现类

  • Spring的存放配置的位置和Java SPI中配置存放的位置默认的地方是不一样的, Spring的是在META-INF/spring.factories
  • Spring SPI配置名为spring.factories, 不可以更改
  • 配置为Key-Value的格式为接口全类名=实现类全类名, 多个实现类的化, Value使用逗号间隔

先让我们整一个Spring SPI的Demo玩一下, 把之间的JDK SPI改造成Spring SPI

  1. resource目录下的META-INF下新建spring.factories文件, 文件中输入下述内容

    com.whitebrocade.Logger=com.whitebrocade.MyLogger, com.whitebrocade.CustomLogger
    
  2. 新建一个测试方法

@Test
public void test() {
    List<Logger> loggerList = SpringFactoriesLoader.loadFactories(Logger.class, this.getClass().getClassLoader());
    for (Logger logger : loggerList) {
        logger.log("Hello, Spring SPI!");
    }
}

// 输出结果如下
// 自定义log实现:Hello, Spring SPI!
// 自定义log实现2:Hello, Spring SPI!
Spring SPI机制原理解析
loadFactories()
public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
    Assert.notNull(factoryType, "'factoryType' must not be null");
    ClassLoader classLoaderToUse = classLoader;
    if (classLoaderToUse == null) {
        // 确定类加载器
        classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    }
    // 解析和加载MEAT-INF下的文件
    List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
    if (logger.isTraceEnabled()) {
        logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
    }
    List<T> result = new ArrayList<>(factoryImplementationNames.size());
    for (String factoryImplementationName : factoryImplementationNames) {
        result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
    }
    AnnotationAwareOrderComparator.sort(result);
    return result;
}
loadFactoryNames() 和 loadSpringFactories()
// org.springframework.core.io.support.SpringFactoriesLoader#loadFactoryNames
// 使用给定的类加载器从 “meta-inf/spring.factories” 加载给定类型的工厂实现的完全限定类名
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    String factoryTypeName = factoryType.getName();
    return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

// org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    MultiValueMap<String, String> result = cache.get(classLoader);
    if (result != null) {
        return result;
    }

    try {
        // public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
        Enumeration<URL> urls = (classLoader != null ?
                                 classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                                 ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        result = new LinkedMultiValueMap<>();
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                String factoryTypeName = ((String) entry.getKey()).trim();
                for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                    result.add(factoryTypeName, factoryImplementationName.trim());
                }
            }
        }
        cache.put(classLoader, result);
        return result;
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                                           FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}
instantiateFactory
@SuppressWarnings("unchecked")
private static <T> T instantiateFactory(String factoryImplementationName, Class<T> factoryType, ClassLoader classLoader) {
    try {
        // 判断factoryImplementationClass对象是不是factoryType的实现类或者子类
        Class<?> factoryImplementationClass = ClassUtils.forName(factoryImplementationName, classLoader);
        // factoryImplementationClass是factoryType的子类或实现类
        // 如果是, 那么就调用factoryImplementationClass的无参构造器初始化对象, 并转成factoryType类型
        // 这里的factoryType相当于接口, factoryImplementationClass相当于实现类
        // 对于我们的程序
        // factoryType就是interface com.whitebrocade.Logger
        // factoryImplementationClass就是class com.whitebrocade.MyLogger
        if (!factoryType.isAssignableFrom(factoryImplementationClass)) {
            throw new IllegalArgumentException(
                "Class [" + factoryImplementationName + "] is not assignable to factory type [" + factoryType.getName() + "]");
        }
        return (T) ReflectionUtils.accessibleConstructor(factoryImplementationClass).newInstance();
    }
    catch (Throwable ex) {
        throw new IllegalArgumentException(
            "Unable to instantiate factory class [" + factoryImplementationName + "] for factory type [" + factoryType.getName() + "]",
            ex);
    }
}

SPI 应用场景

日常开发要不要使用SPI?

不推荐, 主要原因还是我们可以使用更优雅的方式来替代SPI机制,比如:

  • 动态初始化、策略选择: 我们可以使用策略+工厂模式实现策略的动态选择
  • 解耦: 基于良好的设计,可以很容易的实现解耦
框架/组件工具开发要不要使用SPI?

基本都会使用到SPI, 现在的诸多框架及工具就是使用SPI来实现的,引入了SPI机制后,服务接口与服务实现就会达成分离的状态,可以实现解耦以及可扩展机制

Java实现的SPI版本相对比较粗糙和暴力,导致它会把所有接口实现类全部实例化一遍,所以还有框架会对Java的SPI进行封装和优化

参考资料

java菜鸟到大佬——全网最全SPI机制讲解

理解的Java中SPI机制

深入理解 Java 中 SPI 机制

一文搞懂Spring的SPI机制(详解与运用实战)

springboot-starter中的SPI 机制

深入剖析Spring Boot 的SPI机制

「一探究竟」Java SPI机制

你可能感兴趣的:(java,spring,开发语言)