语言特性 | SPI 机制及源码分析 (含自定义迭代器)

admin 2024年9月21日20:42:16评论26 views字数 3626阅读12分5秒阅读模式

SPI 机制及源码分析

基础环境准备

创建父工程后删除 src 目录, 操作流程如下:

语言特性 | SPI 机制及源码分析 (含自定义迭代器)

分别创建: SpiInterface, SpiObject01, SpiObject02子项目, 夫项目是SpiParent. 最终创建如下结构:

语言特性 | SPI 机制及源码分析 (含自定义迭代器)

SPI 实现

准备类与类的关系

SpiInterface项目中定义接口如下:

package com.interfaces;public interface Animal {    public void call();}

SpiObject01项目中定义类, 实现Animal接口如下:

package com.objects;import com.interfaces.Animal;public class Cat implements Animal {    @Override    public void call() {        System.out.println("喵喵喵...");    }}

当然要引用Animal, 那么pom.xml文件中必须进行导入SpiInterface模块:

<dependencies>    <dependency>        <groupId>com.heihu577</groupId>        <artifactId>SpiInterface</artifactId>        <version>1.0-SNAPSHOT</version>        <scope>compile</scope>    </dependency></dependencies>

SpiObject02项目中定义类, 实现Animal接口如下:

package com.Object;import com.interfaces.Animal;public class Dog implements Animal {    @Override    public void call() {        System.out.println("汪汪汪...");    }}

导入模块:

<dependencies>    <dependency>        <groupId>com.heihu577</groupId>        <artifactId>SpiInterface</artifactId>        <version>1.0-SNAPSHOT</version>        <scope>compile</scope>    </dependency></dependencies>

创建测试模块

这里再多创建一个子项目, 用于测试.

语言特性 | SPI 机制及源码分析 (含自定义迭代器)

可以看到的是, 当我们引入的模块不同, 我们需要实例化的对象不同. 例如: 引入SpiObject02我们需要new Dog, 引入SpiObject01我们需要new Cat.

这里需要程序员手动的去引入模块, 手动的编写代码指明实例化谁, 实际上不是很方便. 如果我们手动引入模块之后, 使JAVA程序自动的对它进行实例化岂不是很方便. SPI 解决的就是这个问题, 我们下面看一下具体如何实现.

实现方法

我们需要在SpiObject01 && SpiObject02resources中创建对应的文件:

语言特性 | SPI 机制及源码分析 (含自定义迭代器)

随后在我们的测试模块中修改pom.xml:

<dependencies>    <dependency>        <groupId>com.heihu577</groupId>        <artifactId>SpiObject02</artifactId> <!-- 引入 Dog -->        <version>1.0-SNAPSHOT</version>        <scope>compile</scope>    </dependency>    <dependency>        <groupId>com.heihu577</groupId>        <artifactId>SpiObject01</artifactId> <!-- 引入 Cat -->        <version>1.0-SNAPSHOT</version>        <scope>compile</scope>    </dependency></dependencies>

同时进行引入了, 修改主程序代码如下, 观察运行结果:

public class Main {    public static void main(String[] args) {        ServiceLoader<Animal> animals = ServiceLoader.load(Animal.class);        for (Animal animal : animals) {            System.out.print(animal.getClass() + " - ");            animal.call();            /*            class com.Object.Dog - 汪汪汪...            class com.objects.Cat - 喵喵喵...            * */        }    }}

引入什么模块, ServiceLoader会自动读取模块中META-INF/services目录下的文件, 然后进行读取文件内容, 并通过Class.forName进行实例化, 下面可以研究一下ServiceLoader的底层机制.

自定义迭代器

快速入门

在 SPI 源码中, 里面有自定义迭代器的参与, 所以在这里研究一下自定义迭代器的使用.

public class T1 {    public static void main(String[] args) {        MyIterable strings = new MyIterable(new String[]{"a", "b", "c", "d", "e", "f"});        for (String str : strings) { // 本质上还是调用的 Iterable 的 iterator() 方法中的 hasNext() 判断下一位, 调用 next() 方法取出值            System.out.print(str); // abcdef        }    }}class MyIterable implements Iterable<String> {    private String[] elements;    public MyIterable(String[] elements) {        this.elements = elements; // 初始化一个数组    }    @Override    public Iterator<String> iterator() { // 实现 Iterable 接口必须提供 iterator 方法        return new MyIterator();    }    private class MyIterator implements Iterator<String> {        private int index = 0; // 定义一个索引, 每次判断 hasNext        @Override        public boolean hasNext() {            return elements.length > index; // 判断下一个规则        }        @Override        public String next() {            return elements[index++]; // 取出规则        }    }}

迭代器理解

迭代器比较知名的是我们常用的 ArrayList, 那么我们通过 ArrayList来进行理解迭代器的具体实现.

语言特性 | SPI 机制及源码分析 (含自定义迭代器)

通过继承图可以看到, ArrayList最终实现了Iterable接口, 可以看一下该接口声明:

语言特性 | SPI 机制及源码分析 (含自定义迭代器)

这里注释写的很明确, 如果一个类实现了Iterable接口, 那么这个类是可以使用增强FOR循环的.

ArrayList中定义的iterator()方法做了什么, 我们可以看一下:

语言特性 | SPI 机制及源码分析 (含自定义迭代器)

简单一句话概括: 实现 Iterable 是为了增强 For 循环的使用, 实现 Iterator 是为了定义迭代规则.

SPI 底层源码分析

终于可以进行分析我们SPI的底层源码了, 我们准备如下DEMO进行DEBUG:

public class Main {    public static void main(String[] args) {        ServiceLoader<Animal> animals = ServiceLoader.load(Animal.class); // 断点打到这里进行 DEBUG        Iterator<Animal> iterator = animals.iterator();        while (iterator.hasNext()) {            Animal animal = iterator.next();            System.out.println(animal);        }    }}

看一下ServiceLoader.load方法到底做了一些什么事情:

语言特性 | SPI 机制及源码分析 (含自定义迭代器)

可以看到代码的核心是lookupIterator属性的初始化, 该属性是一个Iterator, 并且在该Iterator中放置了接口类型, 当前ClassLoader这两个属性.

而因为ServiceLoader类是可迭代的, 也可以使用增强FOR循环, 所以该类实现了Iterable接口以及定义了iterator方法, 如图:

语言特性 | SPI 机制及源码分析 (含自定义迭代器)

我们这里可以DEBUG跟进看一下hasNext方法做了什么:

语言特性 | SPI 机制及源码分析 (含自定义迭代器)

这里的核心就是读取/META-INF/services/接口文件, 读取之后取出值放入到names这个ArrayList中, 我们继续看一下next()方法是如何运行的:

语言特性 | SPI 机制及源码分析 (含自定义迭代器)

最终可以看到成功使用Class.forName以及成功对其newInstance()操作.

原文始发于微信公众号(Heihu Share):语言特性 | SPI 机制及源码分析 (含自定义迭代器)

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年9月21日20:42:16
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   语言特性 | SPI 机制及源码分析 (含自定义迭代器)https://cn-sec.com/archives/3192447.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息