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可以实现解耦,可以使得调用者与服务者自由扩展,而不是耦合在一起,可以使应用程序能够根据业务需要启用框架扩展或者替换框架组件

相关文章
|
4天前
|
数据采集 人工智能 Java
Java产科专科电子病历系统源码
产科专科电子病历系统,全结构化设计,实现产科专科电子病历与院内HIS、LIS、PACS信息系统、区域妇幼信息平台的三级互联互通,系统由门诊系统、住院系统、数据统计模块三部分组成,它管理了孕妇从怀孕开始到生产结束42天一系列医院保健服务信息。
19 4
|
11天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
39 2
|
1月前
|
Java Apache Maven
Java百项管理之新闻管理系统 熟悉java语法——大学生作业 有源码!!!可运行!!!
文章提供了使用Apache POI库在Java中创建和读取Excel文件的详细代码示例,包括写入数据到Excel和从Excel读取数据的方法。
60 6
Java百项管理之新闻管理系统 熟悉java语法——大学生作业 有源码!!!可运行!!!
|
2月前
|
数据采集 运维 前端开发
【Java】全套云HIS源码包含EMR、LIS (医院信息化建设)
系统技术特点:采用前后端分离架构,前端由Angular、JavaScript开发;后端使用Java语言开发。
87 5
|
3月前
|
Kubernetes jenkins 持续交付
从代码到k8s部署应有尽有系列-java源码之String详解
本文详细介绍了一个基于 `gitlab + jenkins + harbor + k8s` 的自动化部署环境搭建流程。其中,`gitlab` 用于代码托管和 CI,`jenkins` 负责 CD 发布,`harbor` 作为镜像仓库,而 `k8s` 则用于运行服务。文章具体介绍了每项工具的部署步骤,并提供了详细的配置信息和示例代码。此外,还特别指出中间件(如 MySQL、Redis 等)应部署在 K8s 之外,以确保服务稳定性和独立性。通过本文,读者可以学习如何在本地环境中搭建一套完整的自动化部署系统。
71 0
|
15天前
|
人工智能 监控 数据可视化
Java智慧工地信息管理平台源码 智慧工地信息化解决方案SaaS源码 支持二次开发
智慧工地系统是依托物联网、互联网、AI、可视化建立的大数据管理平台,是一种全新的管理模式,能够实现劳务管理、安全施工、绿色施工的智能化和互联网化。围绕施工现场管理的人、机、料、法、环五大维度,以及施工过程管理的进度、质量、安全三大体系为基础应用,实现全面高效的工程管理需求,满足工地多角色、多视角的有效监管,实现工程建设管理的降本增效,为监管平台提供数据支撑。
32 3
|
20天前
|
运维 自然语言处理 供应链
Java云HIS医院管理系统源码 病案管理、医保业务、门诊、住院、电子病历编辑器
通过门诊的申请,或者直接住院登记,通过”护士工作站“分配患者,完成后,进入医生患者列表,医生对应开具”长期医嘱“和”临时医嘱“,并在电子病历中,记录病情。病人出院时,停止长期医嘱,开具出院医嘱。进入出院审核,审核医嘱与住院通过后,病人结清缴费,完成出院。
58 3
|
26天前
|
JavaScript Java 项目管理
Java毕设学习 基于SpringBoot + Vue 的医院管理系统 持续给大家寻找Java毕设学习项目(附源码)
基于SpringBoot + Vue的医院管理系统,涵盖医院、患者、挂号、药物、检查、病床、排班管理和数据分析等功能。开发工具为IDEA和HBuilder X,环境需配置jdk8、Node.js14、MySQL8。文末提供源码下载链接。
|
29天前
|
移动开发 前端开发 JavaScript
java家政系统成品源码的关键特点和技术应用
家政系统成品源码是已开发完成的家政服务管理软件,支持用户注册、登录、管理个人资料,家政人员信息管理,服务项目分类,订单与预约管理,支付集成,评价与反馈,地图定位等功能。适用于各种规模的家政服务公司,采用uniapp、SpringBoot、MySQL等技术栈,确保高效管理和优质用户体验。
|
1月前
|
JSON 前端开发 Java
震惊!图文并茂——Java后端如何响应不同格式的数据给前端(带源码)
文章介绍了Java后端如何使用Spring Boot框架响应不同格式的数据给前端,包括返回静态页面、数据、HTML代码片段、JSON对象、设置状态码和响应的Header。
143 1
震惊!图文并茂——Java后端如何响应不同格式的数据给前端(带源码)
下一篇
无影云桌面