Java SPI源码解析及demo讲解

Java SPI源码解析

1. SPI是什么,有什么用处

SPI全称S而vice Provider Interface,是java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。

系统设计的各个抽象,一般有很多不同的实现方案,比如通过不同类型的配置文件加载配置信息,通过不同的序列化方案实现序列化。一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可插拔的原则。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。

SPI的核心思想就是 解耦

2.使用场景

调用者根据实际需要替换框架的实现策略。

比如常见的例子:

  • 数据库驱动加载类接口实现类的加载,JDBC加载不同类型数据库的驱动
  • 日志实现类加载
  • dubbo中也大量使用SPI的方式实现框架的扩展,不过它对java提供的SPI进行了封装

3.如何使用

实例代码

  1. 定义一组接口,并写出多个实现类
package com.djl.test.spi.api;

/**
 * @ClassName Robot
 * @Description TODO
 * @Author djl
 * @Date 2020-03-18 19:01
 * @Version 1.0
 */
public interface Robot {

    void sayHello();
}
package com.djl.test.spi.java;

import com.djl.test.spi.api.Robot;

/**
 * @ClassName Bumblebee
 * @Description TODO
 * @Author djl
 * @Date 2020-03-18 19:03
 * @Version 1.0
 */
public class Bumblebee implements Robot {
    @Override
    public void sayHello() {
        System.out.println("Hello ,I am Bumble bee");
    }
}
package com.djl.test.spi.java;

import com.djl.test.spi.api.Robot;

/**
 * @ClassName OptimusPrime
 * @Description TODO
 * @Author djl
 * @Date 2020-03-18 19:02
 * @Version 1.0
 */
public class OptimusPrime implements Robot {
    @Override
    public void sayHello() {
        System.out.println("Hello,I am Optimus prime");
    }
}
  1. 在META-INF/services文件下,创建一个以接口全限定名命名的文件,内容为实现类的全限定名

Java SPI源码解析及demo讲解

com.djl.test.spi.java.Bumblebee
com.djl.test.spi.java.OptimusPrime
  1. 通过ServiceLoader类进行加载
import com.djl.test.spi.api.Robot;
import com.djl.test.spi.api.RobotDubbo;
import org.apache.dubbo.common.extension.ExtensionLoader;
import org.junit.Test;

import java.util.ServiceLoader;

/**
 * @ClassName JavaSPITest
 * @Description TODO
 * @Author djl
 * @Date 2020-03-18 19:06
 * @Version 1.0
 */
public class JavaSPITest {

    @Test
    public void sayHello(){
        ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class);
        System.out.println("java spi");
        serviceLoader.forEach(Robot::sayHello);
    }
}
  1. 执行结果

Java SPI源码解析及demo讲解

4 源码解析

我会将分析都写在代码注释中,大家可以打开自己的源码耐心的看一会。

接下来是重头戏了,知道了spi怎么用,那么内部是如何实现的呢?

我们直接从ServiceLoader类的load方法看起。

/**为给定的服务类型创建一个新的服务加载程序,使用
*当前线程的{@linkplain java.lang.Thread#getContextClassLoader
*上下文类装入器}。
*/
public static <S> ServiceLoader<S> load(Class<S> service) {
    //1.获取当前线程的类加载
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}
public static <S> ServiceLoader<S> load(Class<S> service,
                                        ClassLoader loader)
{
    //new一个serviceloader对象
    return new ServiceLoader<>(service, loader);
}
//构造函数
private ServiceLoader(Class<S> svc, ClassLoader cl) {
    //判断入参是否为null
    service = Objects.requireNonNull(svc, "Service interface cannot be null");
    //2.加载器如果不存在,获取系统类加载器,通常是applicationLoader,应用程序加载器
    loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
    //3.获取访问控制器
    acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
    reload();
}
public void reload() {
    // 清空缓存
    providers.clear();
    // 初始化内部类,用于遍历提供者
    lookupIterator = new LazyIterator(service, loader);
}

看到这里,相信大家对于初始化的内容有了一定了解,这里面涉及到了一些属性,我们来总结

private static final String PREFIX = "META-INF/services/";

// 要加载的类
private final Class<S> service;

// 用于加载实现类的类加载器
private final ClassLoader loader;

// 访问控制器
private final AccessControlContext acc;

// 提供者的缓存
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

// 一个内部类,用于遍历实现类
private LazyIterator lookupIterator;

现在我们发现重点就在于LazyIterator这个内部类上,我们获取实现类都看这个内部类了,我们继续来分析

private class LazyIterator
    implements Iterator<S>
{

    Class<S> service;
    ClassLoader loader;
    Enumeration<URL> configs = null;
    Iterator<String> pending = null;
    String nextName = null;

    //构造函数
    private LazyIterator(Class<S> service, ClassLoader loader) {
        this.service = service;
        this.loader = loader;
    }
    
     private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    //获取META-INF/services下文件全称
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        //获取配置文件内具体实现的枚举类
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }
private Iterator<String> parse(Class<?> service, URL u)
    throws ServiceConfigurationError
{
    InputStream in = null;
    BufferedReader r = null;
    //存储配置文件中实现类的全限定名
    ArrayList<String> names = new ArrayList<>();
    try {
        in = u.openStream();
        r = new BufferedReader(new InputStreamReader(in, "utf-8"));
        int lc = 1;
        //读取文件内容,这里不多说了,正常的流操作
        while ((lc = parseLine(service, u, r, lc, names)) >= 0);
    } catch (IOException x) {
        fail(service, "Error reading configuration file", x);
    } finally {
        try {
            if (r != null) r.close();
            if (in != null) in.close();
        } catch (IOException y) {
            fail(service, "Error closing configuration file", y);
        }
    }
    return names.iterator();
}
private S nextService() {
    if (!hasNextService())
        throw new NoSuchElementException();
    //循环遍历获取实现类的全限定名
    String cn = nextName;
    nextName = null;
    Class<?> c = null;
    try {
        //实例化实现类
        c = Class.forName(cn, false, loader);
    } catch (ClassNotFoundException x) {
        fail(service,
             "Provider " + cn + " not found");
    }
    if (!service.isAssignableFrom(c)) {
        fail(service,
             "Provider " + cn  + " not a subtype");
    }
    try {
        //这一行将实例化的类强转成所表示的类型
        S p = service.cast(c.newInstance());
        //缓存实现类
        providers.put(cn, p);
        //返回对象
        return p;
    } catch (Throwable x) {
        fail(service,
             "Provider " + cn + " could not be instantiated",
             x);
    }
    throw new Error();          // This cannot happen
}

Java SPI源码解析及demo讲解

//这里是iterable循环遍历
default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

到这里整个链路就分析完成了。

感兴趣的小伙伴可以按照demo,自己跑一遍,有问题欢迎提问

原文 

https://segmentfault.com/a/1190000022101812

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » Java SPI源码解析及demo讲解

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址