JAVA SPI机制及源码解析

SPI是什么

SPI 全称为 Service Provider Interface 是一种服务提供发现机制,像Java中的数据库驱动java.sql.Driver就使用到了这种机制,还有像SpringBoot的starter加载也是使用类似的思想,Dubbo也基于JAVA SPI思想实现了一套功能更强的 SPI 机制。

简单案例

我们先通过一个简单案例来看看SPI如何使用及效果,这儿有个Robot的接口,我们通过JAVA SPI构建Robot的ServiceLoader然后调用sayHello方法。

public interface Robot {
    void sayHello();
}

public class JavaSPITest {
    @Test
    public void sayHello() throws Exception {
        ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class);
        System.out.println("Java SPI");
        serviceLoader.forEach(Robot::sayHello);
    }
}

以下的Bumblebee和OptimusPrime是Robot的实现类。

public class Bumblebee implements Robot {

    @Override
    public void sayHello() {
        System.out.println("Hello, I am Bumblebee.");
    }
}

public class OptimusPrime implements Robot {

    @Override
    public void sayHello() {
        System.out.println("Hello, I am Optimus Prime.");
    }
}

控制台输出:
Java SPI

源码解析

这儿为啥没有调用实现类的syaHello方法呢(解决办法在最后面,着急的读者可以直接看最后面)?接下来我们通过源码来看看ServiceLoader是如何查找到Robot的实现类并调用的,先看看load方法做了什么。

    public static <S> ServiceLoader<S> load(Class<S> service) {
    	// 获取类加载器,默认是AppClassLoader
        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);
    }
    
    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();
    }

    public void reload() {
    	// 清除缓存
        providers.clear();
        // 内部懒加载迭代器,用于查找provider
        lookupIterator = new LazyIterator(service, loader);
    }
   

可以发现源码创建了一个ServiceLoader对象和一个LazyIterator对象,我们看看ServiceLoader和LazyIterator的构造器做了什么和内部都有哪些属性。

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
    // 缓存provider,按照实例化顺序
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

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

再看看LazyIterator。

   private class LazyIterator
        implements Iterator<S>
    {
    	// 父类或者接口
        Class<S> service;
        // 类加载器
        ClassLoader loader;
        Enumeration<URL> configs = null;
        // 所有provider的全限定类名
        Iterator<String> pending = null;
        // 下个provider的全限定类名
        String nextName = null;

        private LazyIterator(Class<S> service, ClassLoader loader) {
            this.service = service;
            this.loader = loader;
        }
        ...
    }

通过查看ServiceLoader和LazyIterator源码,发现两个类都是没有静态代码块的,也没有父类,那就是说ServiceLoader.load(Robot.class)这行代码仅仅是做了一些属性赋值的操作,那是如何查找和何时查找provider的呢?有些读者可能已经想到了这儿是使用了懒加载的思想,答案是否如此我们接着顺着JavaSPITest的调用代码往下看。

serviceLoader.forEach(Robot::sayHello)这儿是因为ServiceLoader实现了Iterable接口,我们看看ServiceLoader内部是如何实现iterator方法的。

    public Iterator<S> iterator() {
        return new Iterator<S>() {

            Iterator<Map.Entry<String,S>> knownProviders
                = providers.entrySet().iterator();

            public boolean hasNext() {
                if (knownProviders.hasNext())
                    return true;
                return lookupIterator.hasNext();
            }

            public S next() {
                if (knownProviders.hasNext())
                    return knownProviders.next().getValue();
                return lookupIterator.next();
            }

            public void remove() {
                throw new UnsupportedOperationException();
            }

        };
    }

看源码我们可以发现,iterator方法直接创建了一个匿名内部类,只有一个knownProviders属性,这个属性是匿名内部类被创建时直接拿取外部类ServiceLoader的providers属性的Iterator。除此之外hasNext和next方法都是先直接调用knownProviders的hasNext和next方法,查不到再通过lookupIterator查询,其实这儿的knownProviders就相当于一个缓存,提高性能用的。

之前我们看过ServiceLoader的源码,刚开始的时候providers是没有值的,那么匿名内部类的hasNext和next方法会直接调用lookupIterator的hasNext和next方法,我们看看lookupIterator的完整代码。

private class LazyIterator
        implements Iterator<S>
    {
    	// 父类或者接口
        Class<S> service;
        // 类加载器
        ClassLoader loader;
        Enumeration<URL> configs = null;
        // 所有provider的全限定类名
        Iterator<String> pending = null;
        // 下个provider的全限定类名
        String nextName = null;

        private LazyIterator(Class<S> service, ClassLoader loader) {
            this.service = service;
            this.loader = loader;
        }

        private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            // 初次进来肯定是null,进入内部开始初始化
            if (configs == null) {
                try {
                	// 先构建资源文件的完整路径,然后加载资源文件
                	// 这儿的fullName就是META-INF/services/com.demo.spi.Robot
                    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);
                }
            }
            // 刚开始pending也是null,直接通过上面加载好的configs构建pending
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                // parse方法内部按照行读取赋值
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }

        private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
            	// 通过java反射构建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 {
            	// 实例化对象并转化成指定范型
                S p = service.cast(c.newInstance());
                // 放到provider中
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error();          // This cannot happen
        }

        public boolean hasNext() {
            if (acc == null) {
                return hasNextService();
            } else {
                PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                    public Boolean run() { return hasNextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

        public S next() {
            if (acc == null) {
                return nextService();
            } else {
                PrivilegedAction<S> action = new PrivilegedAction<S>() {
                    public S run() { return nextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

    }

hashNext内部调用了hasNextService方法,hasNextService先是去加载META-INF/services/com.demo.spi.Robot文件,然后按照行读取放到pending中,nextName用于遍历pending,这个pending中的每个String用来做什么呢?我们看看next方法。

next方法调用了nextService方法,nextService直接通过nextName反射构建目标class类,然后调用class的newInstance方法创建目标class的实例对象,放到provider中,这样外面就可以通过provider调用Robot实现类的sayHello方法了。

配置文件

通过源码我们可以知道除了Robot接口、Robot的实现类、调用代码外我们还需要在META-INF/services/文件夹下创建com.demo.spi.Robot文件,里面每一行都是Robot接口的实现类的全限定类名,如下:
在这里插入图片描述

再运行JavaSPITest的代码,控制台输出:
Java SPI
Hello, I am Bumblebee.
Hello, I am Optimus Prime.

知识小结

  1. JAVA SPI采用了懒加载,在真正遍历使用的时候才会去加载类。
  2. JAVA SPI是通过加载读取META-INF/services/文件夹下的接口全限定类名文件(如文中的com.demo.spi.Robot文件),文件中的每一行都需要是接口的实现类的全限定类名,ServiceLoader内部会通过Class.forName反射创建接口实现类实例并维护。
  3. JAVA SPI实现了接口和实现解偶,这也是面向接口编程的一种体现。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值