详解JAVA -SPI机制

一、为何写这篇文章?

在看源码的过程中,总是会遇到SPI机制的源码实现,比如Dubbo,sharding-jdbc ,SpringBoot等源码都涉及到了SPI机制。今天主要是分析一下Java SPI的原理。

二、 何为SPI?

SPI全称(Service Provider Interface)是JDK内置的一种 服务提供发现机制。简单的理解,我觉得他就是一种动态的服务发现机制。
它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。
Java中SPI机制主要思想是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,其核心思想就是 解耦
在这里插入图片描述

三、SPIdemo

1)我们先定义一个接口:

/**
 * @description:定义接口
 * @author: huoyajing
 * @time: 2021/12/22 9:06 上午
 */

public interface Animal {

	void noise();
}

2)写两个实现类实现接口

/**
 * @description:实现类cat
 * @author: huoyajing
 * @time: 2021/12/22 9:07 上午
 */

public class Cat implements Animal {
	@Override
	public void noise() {
		System.out.println("小猫 miao");
	}
}
/**
 * @description:实现类dog
 * @author: huoyajing
 * @time: 2021/12/22 9:08 上午
 */

public class Dog implements Animal {
	@Override
	public void noise() {
		System.out.println("小狗 wang");
	}
}

3)要在ClassPath路径下配置添加一个文件

文件名字是所写的接口包路径,也就是全路径;内容就是想要实现的实现类,想实现几个写几个的全路径。
在这里插入图片描述

4)测试

只会输出文件中配置好的实现结果

public class Test {
	public static void main(String[] args) {
		ServiceLoader<Animal> load = ServiceLoader.load(Animal.class);
		load.forEach(loadInfo -> loadInfo.noise());
	}
}

在这里插入图片描述

四、源码分析

1)ServiceLoader结构

在这里插入图片描述

2)一些常量说明

public final class ServiceLoader<S>
    implements Iterable<S>
{
    // 配置文件的目录
    private static final String PREFIX = "META-INF/services/";

    // The class or interface representing the service being loaded
    // 要加载服务的类或者接口
    private final Class<S> service;

    // The class loader used to locate, load, and instantiate providers
    // 服务加载器
    private final ClassLoader loader;

    // The access control context taken when the ServiceLoader is created
    // 访问控制上下文
    private final AccessControlContext acc;

    // Cached providers, in instantiation order
    // 服务实例的缓存
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

    // The current lazy-lookup iterator
    // 懒加载的迭代器
    private LazyIterator lookupIterator;

3)源码执行流程

public static void main(String[] args) {
		ServiceLoader<Animal> load = ServiceLoader.load(Animal.class);
		load.forEach(loadInfo -> loadInfo.noise());
	}

使用当前线程的上下文类加载器为给定的服务类型创建一个新的服务加载器。
调用这个方法ServiceLoader.load(service)
相当于
ServiceLoader
.load(service,Thread.currentThread().getContextClassLoader())

 public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

为给定的服务类型和类加载器创建一个新的服务加载器。
参数:
service – 代表服务的接口或抽象类
loader – 用于加载提供者配置文件和提供者类的类加载器,如果要使用系统类加载器(或者,如果失败,引导类加载器),则为null

public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {
        return new ServiceLoader<>(service, loader);
    }
//私有构造函数
private ServiceLoader(Class<S> svc, ClassLoader cl) {
        //要加载的接口
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
         //类加载器
         //使用指定类加载器,如果没有指定类加载器,使用系统类加载器,就是应用类加载器。
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }

//重新加载,就相当于重新创建ServiceLoader了,用于新的服务提供者安装到正在运行的Java虚拟机中的情况。
public void reload() {
        //清空缓存中所有已实例化的服务提供者
        providers.clear();
        //新建一个迭代器,该迭代器会从头查找和实例化服务提供者
        lookupIterator = new LazyIterator(service, loader);
    }

4)主要流程LazyIterator

结合上面代码可以看到load方法最后就是创建了一个 new LazyIterator(ServiceLoader的内部类)实例,并没有发现加载配置文件和服务实现,因此ServiceLoader.load方法并没有做真正的加载操作,主要逻辑都在LazyIterator的hasNextService和nextService方法中。

  • 遍历所有存在的service实现
    遍历所有存在的service实现
  • private static final String PREFIX = “META-INF/services/”;
    /置文件的目录,固定路径
 private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                	//通过相对路径读取classpath中META-INF目录的文件,也就是读取服务提供者的实现类全限定名
                    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);
                }
            }
            //判断是否读取到实现类全限定名,比如mysql的“com.mysql.jdbc.Driver
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            //nextName保存,后续初始化实现类使用
            nextName = pending.next();
            return true;
        }
public S next() {
            //用来判断serviceLoader对象是否完成初始化
            if (acc == null) {
                return nextService();
            } else {
                PrivilegedAction<S> action = new PrivilegedAction<S>() {
                    public S run() { return nextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }
private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            //上一步找到的服务实现者全限定名
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                //加载字节码返回class对象.但并不去初始化(换句话就是说不去执行这个类中的static块与static变量初始化)
                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 {
                //初始化这个实现类.将会通过static块的方式触发实现类注册到DriverManager(其中组合了一个CopyOnWriteArrayList的registeredDrivers成员变量)中
                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机制的优势是实现解耦,使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离,而不是耦合在一起。应用程序可以根据实际业务情况启用框架扩展或替换框架组件。

缺点

  • 虽然ServiceLoader也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。如果你并不想用某些实现类,它也被加载并实例化了,这就造成了浪费。获取某个实现类的方式不够灵活,只能通过Iterator形式获取,不能根据某个参数来获取对应的实现类。
  • 多个并发多线程使用ServiceLoader类的实例是不安全的。

Dubbo SPI 实现方式对以上两点进行了业务优化。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值