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;

上述为 ServiceLoader成员属性

那ServiceLoad.load 干了什么??

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);
    }
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();
  }
  public void reload() {
        // 先把之前的缓存清了
        providers.clear();
        // 创建懒迭代器对象。
        lookupIterator = new LazyIterator(service, loader);
    }

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

    }

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

可以看出进入一个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();
    }

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

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

        };
    }

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

总结

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

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

优点:

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

相关文章
|
6天前
|
XML Java 编译器
Java注解的底层源码剖析与技术认识
Java注解(Annotation)是Java 5引入的一种新特性,它提供了一种在代码中添加元数据(Metadata)的方式。注解本身并不是代码的一部分,它们不会直接影响代码的执行,但可以在编译、类加载和运行时被读取和处理。注解为开发者提供了一种以非侵入性的方式为代码提供额外信息的手段,这些信息可以用于生成文档、编译时检查、运行时处理等。
32 7
|
18天前
|
数据采集 人工智能 Java
Java产科专科电子病历系统源码
产科专科电子病历系统,全结构化设计,实现产科专科电子病历与院内HIS、LIS、PACS信息系统、区域妇幼信息平台的三级互联互通,系统由门诊系统、住院系统、数据统计模块三部分组成,它管理了孕妇从怀孕开始到生产结束42天一系列医院保健服务信息。
28 4
|
24天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
64 2
|
2月前
|
Java Apache Maven
Java百项管理之新闻管理系统 熟悉java语法——大学生作业 有源码!!!可运行!!!
文章提供了使用Apache POI库在Java中创建和读取Excel文件的详细代码示例,包括写入数据到Excel和从Excel读取数据的方法。
61 6
Java百项管理之新闻管理系统 熟悉java语法——大学生作业 有源码!!!可运行!!!
|
3月前
|
数据采集 运维 前端开发
【Java】全套云HIS源码包含EMR、LIS (医院信息化建设)
系统技术特点:采用前后端分离架构,前端由Angular、JavaScript开发;后端使用Java语言开发。
110 5
|
12天前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
42 12
|
4月前
|
Kubernetes jenkins 持续交付
从代码到k8s部署应有尽有系列-java源码之String详解
本文详细介绍了一个基于 `gitlab + jenkins + harbor + k8s` 的自动化部署环境搭建流程。其中,`gitlab` 用于代码托管和 CI,`jenkins` 负责 CD 发布,`harbor` 作为镜像仓库,而 `k8s` 则用于运行服务。文章具体介绍了每项工具的部署步骤,并提供了详细的配置信息和示例代码。此外,还特别指出中间件(如 MySQL、Redis 等)应部署在 K8s 之外,以确保服务稳定性和独立性。通过本文,读者可以学习如何在本地环境中搭建一套完整的自动化部署系统。
73 0
|
6天前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
28天前
|
人工智能 监控 数据可视化
Java智慧工地信息管理平台源码 智慧工地信息化解决方案SaaS源码 支持二次开发
智慧工地系统是依托物联网、互联网、AI、可视化建立的大数据管理平台,是一种全新的管理模式,能够实现劳务管理、安全施工、绿色施工的智能化和互联网化。围绕施工现场管理的人、机、料、法、环五大维度,以及施工过程管理的进度、质量、安全三大体系为基础应用,实现全面高效的工程管理需求,满足工地多角色、多视角的有效监管,实现工程建设管理的降本增效,为监管平台提供数据支撑。
36 3
|
8天前
|
人工智能 移动开发 安全
家政上门系统用户端、阿姨端源码,java家政管理平台源码
家政上门系统基于互联网技术,整合大数据分析、AI算法和现代通信技术,提供便捷高效的家政服务。涵盖保洁、月嫂、烹饪等多元化服务,支持多终端访问,具备智能匹配、在线支付、订单管理等功能,确保服务透明、安全,适用于家庭生活的各种需求场景,推动家政市场规范化发展。