Java SPI源码及示例如此简单!

1. Java SPI 概述

SPI(Service Provider Interface)即服务提供接口,是JDK内置的一种服务提供发现机制。简单来说,它就是一种动态替换发现机制。在程序启动时加载配置文件,在程序调用的时候才通过反射去实例化具体的实现类。Java提供了很多默认的SPI,允许第三方为这些接口提供实现。常见的SPI有JDBC、JCE、JNDI、JAXP和JBI等。
SPI的接口是Java核心库的一部分所以是由BootstrapClassloader(引导类加载器)来加载的,SPI的实现类是由AppClassLoader(应用类加载器)来加载的。因此BootstrapClassloader是无法找到SPI的实现类的(双亲委派模型),顾需要引入线程上下文类加载(后面源码分析可以看到)来破坏类加载的双亲委派模型,使得程序可以进行逆向类加载。
SPI和API(Application Programming Interface)的区别可以简单理解为:API是由开发人员制定接口并完成对接口的实现,暴露接口供外部人员调用;SPI则是调用方来制定接口规范,外部扩展人员来提供具体实现。

2. Java SPI 示例

2.1 创建示例maven工程

在这里插入图片描述

2.2 编写SPI接口及实现(一般是外部使用人员扩展实现)
package com.qqxhb.spi.java;

public interface IAnimal {
	void say();
}
package com.qqxhb.spi.java;

public class Cat implements IAnimal {

	@Override
	public void say() {
		System.out.println("猫叫========");
	}
}
package com.qqxhb.spi.java;
public class Dog implements IAnimal {

	@Override
	public void say() {
		System.out.println("狗吠========");
	}
}
2.3 编写接口配置文件

在classpath下创建META-INF/services文件夹,在文件夹下创建具体接口全限定名文件,在文件中配置接口的具体实现(类全限定名)。本例com.qqxhb.spi.java.IAnimal文件内容如下:

com.qqxhb.spi.java.Cat
com.qqxhb.spi.java.Dog
2.4 使用ServiceLoader加载实现类并调用
package com.qqxhb.spi.java;

import java.util.ServiceLoader;

public class JavaSPI {

	public static void main(String[] args) {
		ServiceLoader<IAnimal> serviceLoader = ServiceLoader.load(IAnimal.class);
		serviceLoader.forEach(IAnimal::say);
	}

}

调用成功,结果在控制台打印:

猫叫========
狗吠========

3. Java SPI 加载源码分析

  1. 根据上面的实例我们查看java.util.ServiceLoader.load(Class)方法源码
    public static <S> ServiceLoader<S> load(Class<S> service) {
		//获取线程上下文类加载器
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
		//调用重载方法
        return ServiceLoader.load(service, cl);
    }
	
    public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {	//重载方法直接创建了一个ServiceLoader实例
        return new ServiceLoader<>(service, loader);
    }
  1. 继续上面的源码,查看ServiceLoader的属性和构造函数,省略部分源码
public final class ServiceLoader<S>
    implements Iterable<S>
{
	//服务路径前缀,写死的因此自定义的配置SPI服务必须放到这个路径下
    private static final String PREFIX = "META-INF/services/";

    // 表示正在加载的服务的类或接口
    private final Class<S> service;

    // 用于定位、加载和实例化提供程序的类加载器
    private final ClassLoader loader;

    // The access control context taken when the ServiceLoader is created
    private final AccessControlContext acc;

    // 创建ServiceLoader时获取的访问控制上下文
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

    // 延迟加载迭代器
    private LazyIterator lookupIterator;

    /**
     * 清空服务提供者(即实现类实例)
	 * 初始化延迟加载迭代器
     */
    public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }
	//私有化构造函数,简单对属性赋值,并调用reload方法
    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();
    }
}

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

		// 迭代器的hasNext()主要调用这个方法实现的,这个方法也负责加载配置文件
		private boolean hasNextService() {
			// nextName是下一个服务实现类的全限定名,存在则直接返回他true
            if (nextName != null) {
                return true;
            }
			// 配置为空则会去加载配置文件
            if (configs == null) {
                try {
					//文件路径是上面提到的前缀+服务(接口全限定名)
                    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;
                }
				//parse方法会调用parseLine方法,就是简单的按行加载配置文件内容
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }
		// 迭代器next方法调用本方法实现,负责反射实例化实现类
        private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
				//根据名称获取class对象
                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 {
				//反射创建实例,并存放到providers 中
                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
        }

示例源码:https://github.com/qqxhb/dubbo-demo/tree/master/dubbo-source-code-demo
Dubbo SPI内容请参考下篇博客:一篇短文搞定Dubbo SPI 源码及示例

发布了131 篇原创文章 · 获赞 7 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_43792385/article/details/105286075