如何快速掌握Java编程技能?
- 内容介绍
- 文章标签
- 相关推荐
本文共计1919个文字,预计阅读时间需要8分钟。
目录 + SPI + 概念 + 代表子 + 第一步 + 第二步 + 第三步 + 第四步 + 原理 + 常用框架 + 优缺点 + 优点 + 缺点 + Java + 程序员在日常工作经常听到 SPI,很多框架都使用了 SPI 技术,那么问题来了…
目录
- SPI 概念
- 举个栗子
- 第一步
- 第二步
- 第三步
- 第四步
- 原理
- 常用的框架
- 优缺点
- 优点
- 缺点
Java程序员在日常工作中经常会听到SPI,而且很多框架都使用了SPI的技术,那么问题来了,到底什么是SPI呢?今天阿粉就带大家好好了解一下 SPI。
SPI 概念
SPI全称是Service Provider Interface,是一种JDK内置的动态加载实现扩展点的机制,通过SPI技术我们可以动态获取接口的实现类,不用自己来创建。
这里提到了接口和实现类,那么SPI技术上具体有哪些技术细节呢?
- 接口:需要有一个功能接口;
- 实现类:接口只是规范,具体的执行需要有实现类才行,所以不可缺少的需要有实现类;
- 配置文件:要实现
SPI机制,必须有一个与接口同名的文件存放于类路径下面的META-INF/services文件夹中,并且文件中的每一行的内容都是一个实现类的全路径; - 类加载器
ServiceLoader:JDK内置的一个类加载器,用于加载配置文件中的实现类;
举个栗子
上面说了SPI的几个概念,接下来阿粉就通过一个栗子来带大家感受一下具体的用法。
第一步
创建一个接口,这里我们创建一个解压缩的接口,其中定义了压缩和解压的两个方法。
package com.example.demo.spi; /** * <br> * <b>Function:</b><br> * <b>Author:</b>@author ziyou<br> * <b>Date:</b>2022-10-08 21:31<br> * <b>Desc:</b>无<br> */ public interface Compresser { byte[] compress(byte[] bytes); byte[] decompress(byte[] bytes); }
第二步
再写两个对应的实现类,分别是GzipCompresser.java和WinRarCompresser.java代码如下
package com.example.demo.spi.impl; import com.example.demo.spi.Compresser; import java.nio.charset.StandardCharsets; /** * <br> * <b>Function:</b><br> * <b>Author:</b>@author ziyou<br> * <b>Date:</b>2022-10-08 21:33<br> * <b>Desc:</b>无<br> */ public class GzipCompresser implements Compresser { @Override public byte[] compress(byte[] bytes) { return"compress by Gzip".getBytes(StandardCharsets.UTF_8); } @Override public byte[] decompress(byte[] bytes) { return "decompress by Gzip".getBytes(StandardCharsets.UTF_8); } }
package com.example.demo.spi.impl; import com.example.demo.spi.Compresser; import java.nio.charset.StandardCharsets; /** * * <b>Function:</b> * <b>Author:</b>@author ziyou * <b>Date:</b>2022-10-08 21:33 * <b>Desc:</b>无 */ public class WinRarCompresser implements Compresser { @Override public byte[] compress(byte[] bytes) { return "compress by WinRar".getBytes(StandardCharsets.UTF_8); } @Override public byte[] decompress(byte[] bytes) { return "decompress by WinRar".getBytes(StandardCharsets.UTF_8); } }
第三步
创建配置文件,我们接着在resources目录下创建一个名为META-INF/services的文件夹,在其中创建一个名为com.example.demo.spi.Compresser的文件,其中的内容如下:
com.example.demo.spi.impl.WinRarCompresser com.example.demo.spi.impl.GzipCompresser
注意该文件的名称必须是接口的全路径,文件里面的内容每一行都是一个实现类的全路径,多个实现类就写在多行里面,效果如下。
第四步
有了上面的接口,实现类和配置文件,接下来我们就可以使用ServiceLoader动态加载实现类,来实现SPI技术了,如下所示:
package com.example.demo; import com.example.demo.spi.Compresser; import java.nio.charset.StandardCharsets; import java.util.ServiceLoader; public class TestSPI { public static void main(String[] args) { ServiceLoader<Compresser> compressers = ServiceLoader.load(Compresser.class); for (Compresser compresser : compressers) { System.out.println(compresser.getClass()); } } }
运行的结果如下
可以看到我们正常的获取到了接口的实现类,并且可以直接使用实现类的解压缩方法。
原理
知道了如何使用SPI接下来我们来研究一下是如何实现的,通过上面的测试我们可以看到,核心的逻辑是ServiceLoader.load()方法,这个方法有点类似于Spring中的根据接口获取所有实现类一样。
点开ServiceLoader我们可以看到有一个常量PREFIX,如下所示,这也是为什么我们必须在这个路径下面创建配置文件,因为JDK代码里面会从这个路径里面去读取我们的文件。
同时又因为在读取文件的时候使用了class的路径名称,因为我们使用load方法的时候只会传递一个class,所以我们的文件名也必须是接口的全路径。
通过load方法我们可以看到底层构造了一个java.util.ServiceLoader.LazyIterator迭代器。
在迭代器中的parse方法中,就获取了配置文件中的实现类名称集合,然后在通过反射创建出具体的实现类对象存放到LinkedHashMap<String,S> providers = new LinkedHashMap<>();中。
常用的框架
SPI 技术的使用非常广泛,比如在Dubble,不过Dubble中的SPI有经过改造的,还有我们很常见的数据库的驱动中也使用了SPI,感兴趣的小伙伴可以去翻翻看,还有SLF4J用来加载不同提供商的日志实现类以及Spring框架等。
优缺点
前面介绍了SPI的原理和使用,那SPI有什么优缺点呢?
优点
优点当然是解耦,服务方只要定义好接口规范就好了,具体的实现可以由不同的Jar进行实现,只要按照规范实现功能就可以被直接拿来使用,在某些场合会被进行热插拔使用,实现了解耦的功能。
缺点
一个很明显的缺点那就是做不到按需加载,通过源码我们看到了是会将所有的实现类都进行创建的,这种做法会降低性能,如果某些实现类实现很耗时了话将影响加载时间。同时实现类的命名也没有规范,让使用者不方便引用。
到此这篇关于一文搞懂Java SPI机制的原理与使用的文章就介绍到这了,更多相关Java SPI机制内容请搜索自由互联以前的文章或继续浏览下面的相关文章希望大家以后多多支持自由互联!
本文共计1919个文字,预计阅读时间需要8分钟。
目录 + SPI + 概念 + 代表子 + 第一步 + 第二步 + 第三步 + 第四步 + 原理 + 常用框架 + 优缺点 + 优点 + 缺点 + Java + 程序员在日常工作经常听到 SPI,很多框架都使用了 SPI 技术,那么问题来了…
目录
- SPI 概念
- 举个栗子
- 第一步
- 第二步
- 第三步
- 第四步
- 原理
- 常用的框架
- 优缺点
- 优点
- 缺点
Java程序员在日常工作中经常会听到SPI,而且很多框架都使用了SPI的技术,那么问题来了,到底什么是SPI呢?今天阿粉就带大家好好了解一下 SPI。
SPI 概念
SPI全称是Service Provider Interface,是一种JDK内置的动态加载实现扩展点的机制,通过SPI技术我们可以动态获取接口的实现类,不用自己来创建。
这里提到了接口和实现类,那么SPI技术上具体有哪些技术细节呢?
- 接口:需要有一个功能接口;
- 实现类:接口只是规范,具体的执行需要有实现类才行,所以不可缺少的需要有实现类;
- 配置文件:要实现
SPI机制,必须有一个与接口同名的文件存放于类路径下面的META-INF/services文件夹中,并且文件中的每一行的内容都是一个实现类的全路径; - 类加载器
ServiceLoader:JDK内置的一个类加载器,用于加载配置文件中的实现类;
举个栗子
上面说了SPI的几个概念,接下来阿粉就通过一个栗子来带大家感受一下具体的用法。
第一步
创建一个接口,这里我们创建一个解压缩的接口,其中定义了压缩和解压的两个方法。
package com.example.demo.spi; /** * <br> * <b>Function:</b><br> * <b>Author:</b>@author ziyou<br> * <b>Date:</b>2022-10-08 21:31<br> * <b>Desc:</b>无<br> */ public interface Compresser { byte[] compress(byte[] bytes); byte[] decompress(byte[] bytes); }
第二步
再写两个对应的实现类,分别是GzipCompresser.java和WinRarCompresser.java代码如下
package com.example.demo.spi.impl; import com.example.demo.spi.Compresser; import java.nio.charset.StandardCharsets; /** * <br> * <b>Function:</b><br> * <b>Author:</b>@author ziyou<br> * <b>Date:</b>2022-10-08 21:33<br> * <b>Desc:</b>无<br> */ public class GzipCompresser implements Compresser { @Override public byte[] compress(byte[] bytes) { return"compress by Gzip".getBytes(StandardCharsets.UTF_8); } @Override public byte[] decompress(byte[] bytes) { return "decompress by Gzip".getBytes(StandardCharsets.UTF_8); } }
package com.example.demo.spi.impl; import com.example.demo.spi.Compresser; import java.nio.charset.StandardCharsets; /** * * <b>Function:</b> * <b>Author:</b>@author ziyou * <b>Date:</b>2022-10-08 21:33 * <b>Desc:</b>无 */ public class WinRarCompresser implements Compresser { @Override public byte[] compress(byte[] bytes) { return "compress by WinRar".getBytes(StandardCharsets.UTF_8); } @Override public byte[] decompress(byte[] bytes) { return "decompress by WinRar".getBytes(StandardCharsets.UTF_8); } }
第三步
创建配置文件,我们接着在resources目录下创建一个名为META-INF/services的文件夹,在其中创建一个名为com.example.demo.spi.Compresser的文件,其中的内容如下:
com.example.demo.spi.impl.WinRarCompresser com.example.demo.spi.impl.GzipCompresser
注意该文件的名称必须是接口的全路径,文件里面的内容每一行都是一个实现类的全路径,多个实现类就写在多行里面,效果如下。
第四步
有了上面的接口,实现类和配置文件,接下来我们就可以使用ServiceLoader动态加载实现类,来实现SPI技术了,如下所示:
package com.example.demo; import com.example.demo.spi.Compresser; import java.nio.charset.StandardCharsets; import java.util.ServiceLoader; public class TestSPI { public static void main(String[] args) { ServiceLoader<Compresser> compressers = ServiceLoader.load(Compresser.class); for (Compresser compresser : compressers) { System.out.println(compresser.getClass()); } } }
运行的结果如下
可以看到我们正常的获取到了接口的实现类,并且可以直接使用实现类的解压缩方法。
原理
知道了如何使用SPI接下来我们来研究一下是如何实现的,通过上面的测试我们可以看到,核心的逻辑是ServiceLoader.load()方法,这个方法有点类似于Spring中的根据接口获取所有实现类一样。
点开ServiceLoader我们可以看到有一个常量PREFIX,如下所示,这也是为什么我们必须在这个路径下面创建配置文件,因为JDK代码里面会从这个路径里面去读取我们的文件。
同时又因为在读取文件的时候使用了class的路径名称,因为我们使用load方法的时候只会传递一个class,所以我们的文件名也必须是接口的全路径。
通过load方法我们可以看到底层构造了一个java.util.ServiceLoader.LazyIterator迭代器。
在迭代器中的parse方法中,就获取了配置文件中的实现类名称集合,然后在通过反射创建出具体的实现类对象存放到LinkedHashMap<String,S> providers = new LinkedHashMap<>();中。
常用的框架
SPI 技术的使用非常广泛,比如在Dubble,不过Dubble中的SPI有经过改造的,还有我们很常见的数据库的驱动中也使用了SPI,感兴趣的小伙伴可以去翻翻看,还有SLF4J用来加载不同提供商的日志实现类以及Spring框架等。
优缺点
前面介绍了SPI的原理和使用,那SPI有什么优缺点呢?
优点
优点当然是解耦,服务方只要定义好接口规范就好了,具体的实现可以由不同的Jar进行实现,只要按照规范实现功能就可以被直接拿来使用,在某些场合会被进行热插拔使用,实现了解耦的功能。
缺点
一个很明显的缺点那就是做不到按需加载,通过源码我们看到了是会将所有的实现类都进行创建的,这种做法会降低性能,如果某些实现类实现很耗时了话将影响加载时间。同时实现类的命名也没有规范,让使用者不方便引用。
到此这篇关于一文搞懂Java SPI机制的原理与使用的文章就介绍到这了,更多相关Java SPI机制内容请搜索自由互联以前的文章或继续浏览下面的相关文章希望大家以后多多支持自由互联!

