Java-SPI源码剖析

简介: Java-SPI源码剖析

1、创建

//  SPI接口实现类 要加载的位置前缀    
private static final String PREFIX = "META-INF/services/";
 
// 要加载接口的class对象
    // 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;
AI 代码解读

上述为 ServiceLoader成员属性

那ServiceLoad.load 干了什么??

public static <S> ServiceLoader<S> load(Class<S> service) {
        // 获取当前线程的类加载器
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
}
AI 代码解读
  public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {
        // 创建ServiceLoader对象
        return new ServiceLoader<>(service, loader);
    }
AI 代码解读
private ServiceLoader(Class<S> svc, ClassLoader cl) {
        // 判断一下传入的接口class对象是否合法
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
         // 类加载器,如果线程的classLoad没有,默认采用SystemClassLoader
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        // 权限访问控制
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
  }
AI 代码解读
  public void reload() {
        // 先把之前的缓存清了
        providers.clear();
        // 创建懒迭代器对象。
        lookupIterator = new LazyIterator(service, loader);
    }
AI 代码解读

reload方法, 先是将缓存清了,又创建懒 迭代器对象。这个懒加载迭代器是ServiceLoader的一个内部类。

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 {
                    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 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
        }

        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();
        }

    }
AI 代码解读

ServiceLoader.load核心是清除缓存,创建lazyInterator,其用来类加载,遍历SPI实现类

2、加载

加载是在 LazyIterator 中完成的, 而且是在当我们判断获取的时候才加载

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);
            }
}
AI 代码解读
private boolean hasNextService() {
            
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    // 拼接 路径   META-INF/services/spi接口名称
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        // 获取spi接口实现类url
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            // 第一次的时候 或者 pending没有 下一个元素的时候
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                // 获得一个 迭代器。
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
}
AI 代码解读

可以看出进入一个hasNextService方法。在hasNextService方法中,先是判断一下下一个的元素名有没有,有的话直接返回true。判断config ==null 这个第一次的时候会进入,拼接默认spi接口实现类存放的路径,形成一个url。接着就会解析这个文件,获得一个迭代器对象。这个url实际上就是spi接口文件地址

说白了,上述操作,就是想获取到要加载的指定SPI实现类文件,获取到文件,读取配置项,也即获取SPI接口实现类列表

在这里插入图片描述

 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();
            // 通过BufferedReader来一行一行的读取
            r = new BufferedReader(new InputStreamReader(in, "utf-8"));
            int lc = 1;
             通过BufferedReader来一行一行的读取
            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();
    }
AI 代码解读

parse(service, configs.nextElement())方法。我们可以看出parse方法通过BufferedReader 一行行读取配置文件存入List中,最后返回List的迭代器。

加载的过程,就是找到SPI接口位置,读取SPI接口配置文件,获取其中的实现类

3、获取

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);
            }
        }
AI 代码解读
private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            //保存副本
            String cn = nextName;
            // 设置null
            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 {
                // 创建对象
                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
}
AI 代码解读

获取SPI实现类对象,本质上通过Class.forname 进行类加载获取的,然后放入缓存。

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();
            }

        };
    }
AI 代码解读

经过第一次SPI类加载之后,后续所有遍历操作都直接从缓存中拿,除非重新进行ServiceLoader.load 重新读取SPI接口文件配置项,进行类加载

总结

SPI机制是java提供的扩展机制,主要用来为第三方应用进行扩展用的,自身服务只需要提供SPI接口,第三方应用自己实现SPI接口即可。

SPI原理无非是内部通过LazyInterator进行处理,先找到SPI配置文件地址,逐一读取配置项,进行类加载获取class。当然内部维护一套缓存机制provider,不需要每次都读取SPi配置文件,Class.ForName,优化性能

优点:

SPI可以说是一种插拔机制, 使用SPI可以实现解耦,可以使得调用者与服务者自由扩展,而不是耦合在一起,可以使应用程序能够根据业务需要启用框架扩展或者替换框架组件

目录
打赏
0
0
0
0
5
分享
相关文章
Java 集合面试题从数据结构到 HashMap 源码剖析详解及长尾考点梳理
本文深入解析Java集合框架,涵盖基础概念、常见集合类型及HashMap的底层数据结构与源码实现。从Collection、Map到Iterator接口,逐一剖析其特性与应用场景。重点解读HashMap在JDK1.7与1.8中的数据结构演变,包括数组+链表+红黑树优化,以及put方法和扩容机制的实现细节。结合订单管理与用户权限管理等实际案例,展示集合框架的应用价值,助你全面掌握相关知识,轻松应对面试与开发需求。
109 3
家政系统源码,java版本
这是一款基于SpringBoot后端框架、MySQL数据库及Uniapp移动端开发的家政预约上门服务系统。
家政系统源码,java版本
Java基于SaaS模式多租户ERP系统源码
ERP,全称 Enterprise Resource Planning 即企业资源计划。是一种集成化的管理软件系统,它通过信息技术手段,将企业的各个业务流程和资源管理进行整合,以提高企业的运营效率和管理水平,它是一种先进的企业管理理念和信息化管理系统。 适用于小微企业的 SaaS模式多租户ERP管理系统, 采用最新的技术栈开发, 让企业简单上云。专注于小微企业的应用需求,如企业基本的进销存、询价,报价, 采购、销售、MRP生产制造、品质管理、仓库库存管理、财务应收付款, OA办公单据、CRM等。
171 23
基于Java+Springboot+Vue开发的鲜花商城管理系统源码+运行
基于Java+Springboot+Vue开发的鲜花商城管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java的鲜花商城管理系统项目,大学生可以在实践中学习和提升自己的能力,为以后的职业发展打下坚实基础。技术学习共同进步
262 7
Java汽车租赁系统源码(含数据库脚本)
Java汽车租赁系统源码(含数据库脚本)
68 4
JUC并发—1.Java集合包底层源码剖析
本文主要对JDK中的集合包源码进行了剖析。
|
3月前
|
【源码】【Java并发】【ConcurrentHashMap】适合中学体质的ConcurrentHashMap
本文深入解析了ConcurrentHashMap的实现原理,涵盖JDK 7与JDK 8的区别、静态代码块、构造方法、put/get/remove核心方法等。JDK 8通过Node数组+链表/红黑树结构优化并发性能,采用CAS和synchronized实现高效锁机制。文章还详细讲解了hash计算、表初始化、扩容协助及计数更新等关键环节,帮助读者全面掌握ConcurrentHashMap的工作机制。
96 6
【源码】【Java并发】【ConcurrentHashMap】适合中学体质的ConcurrentHashMap
智慧工地源码,Java语言开发,微服务架构,支持分布式和集群部署,多端覆盖
智慧工地是“互联网+建筑工地”的创新模式,基于物联网、移动互联网、BIM、大数据、人工智能等技术,实现对施工现场人员、设备、材料、安全等环节的智能化管理。其解决方案涵盖数据大屏、移动APP和PC管理端,采用高性能Java微服务架构,支持分布式与集群部署,结合Redis、消息队列等技术确保系统稳定高效。通过大数据驱动决策、物联网实时监测预警及AI智能视频监控,消除数据孤岛,提升项目可控性与安全性。智慧工地提供专家级远程管理服务,助力施工质量和安全管理升级,同时依托可扩展平台、多端应用和丰富设备接口,满足多样化需求,推动建筑行业数字化转型。
114 5
|
3月前
|
【源码】【Java并发】【LinkedBlockingQueue】适合中学体质的LinkedBlockingQueue入门
前言 有了前文对简单实用的学习 【Java并发】【LinkedBlockingQueue】适合初学体质的LinkedBlockingQueue入门 聪明的你,一定会想知道更多。哈哈哈哈哈,下面主播就...
71 6
【源码】【Java并发】【LinkedBlockingQueue】适合中学体质的LinkedBlockingQueue入门
|
3月前
|
【源码】【Java并发】【ArrayBlockingQueue】适合中学者体质的ArrayBlockingQueue
前言 通过之前的学习是不是学的不过瘾,没关系,马上和主播来挑战源码的阅读 【Java并发】【ArrayBlockingQueue】适合初学体质的ArrayBlockingQueue入门 还有一件事
70 5
【源码】【Java并发】【ArrayBlockingQueue】适合中学者体质的ArrayBlockingQueue
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问