Java-SPI机制详解

这篇具有很好参考价值的文章主要介绍了Java-SPI机制详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Java之SPI机制详解

1: SPI机制简介

SPI 全称是 Service Provider Interface,是一种 JDK 内置的动态加载实现扩展点的机制,通过 SPI 技术我们可以动态获取接口的实现类,不用自己来创建。这个不是什么特别的技术,只是 一种设计理念

2: SPI原理

Java-SPI机制详解

Java SPI 实际上是基于接口的编程+策略模式+配置文件组合实现的动态加载机制。

系统设计的各个抽象,往往有很多不同的实现方案,在面向的对象的设计里,一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。

Java SPI就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。所以SPI的核心思想就是解耦

3: 使用场景

调用者根据实际使用需要 启用、扩展、或者替换框架的实现策略

下面是一些使用了该机制的场景

  • JDBC驱动,加载不同数据库的驱动类
  • Spring中大量使用了SPI,比如:对servlet3.0规范对ServletContainerInitializer的实现、自动类型转换Type Conversion SPI(Converter SPI、Formatter SPI)等
  • Dubbo中也大量使用SPI的方式实现框架的扩展, 不过它对Java提供的原生SPI做了封装,允许用户扩展实现Filter接口
  • Tomcat 加载 META-INF/services下找需要加载的类
  • SpringBoot项目中 使用@SpringBootApplication注解时,会开始自动配置,而启动配置则会去扫描META-INF/spring.factories下的配置类

4: 源码论证

4.1 应用程序调用ServiceLoader.load方法
ServiceLoader.load方法内先创建一个新的ServiceLoader,并实例化该类中的成员变量

    private static final String PREFIX = "META-INF/services/";


  private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }

	/** 
     * 
     * 在调用该方法之后,迭代器方法的后续调用将延迟地从头开始查找和实例化提供程序,就像新创建的加载程序所做的		  那样
     */
   public void reload() {
        providers.clear(); //清除此加载程序的提供程序缓存,以便重新加载所有提供程序。
        lookupIterator = new LazyIterator(service, loader);
    }

	private class LazyIterator implements Iterator<S>{

        Class<S> service;
        ClassLoader loader;
        Enumeration<URL> configs = null;
        Iterator<String> pending = null;
        String nextName = null;


        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
        }

5: 实战

步骤1 新建以下类

public interface IService {

    /**
     * 获取价格
     * @return
     */
    String getPrice();

    /**
     * 获取规格信息
     * @return
     */
    String getSpecifications();
}
public class GoodServiceImpl implements IService {

    @Override
    public String getPrice() {
        return "2000.00元";
    }

    @Override
    public String getSpecifications() {
        return "200g/件";
    }
}

public class MedicalServiceImpl implements IService {

    @Override
    public String getPrice() {
        return "3022.12元";
    }

    @Override
    public String getSpecifications() {
        return "30粒/盒";
    }
}

步骤2、在 src/main/resources/ 下建立 /META-INF/services 目录, 新增一个以接口命名的文件 org.example.IService.txt 。内容是要应用的实现类,我这边需要放入的数据如下

org.example.GoodServiceImpl
org.example.MedicalServiceImpl

步骤3、使用 ServiceLoader 来加载配置文件中指定的实现。

public class Main {
    public static void main(String[] args) {
        final ServiceLoader<IService> serviceLoader = ServiceLoader.load(IService.class);
        serviceLoader.forEach(service -> {
            System.out.println(service.getPrice() + "=" + service.getSpecifications());
        });
    }
}

输出:

2000.00元=200g/件
3022.12元=30粒/盒

6: 优缺点

6.1 优点

解耦 使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离,而不是耦合在一起,应用程序可以根据实际业务情况启用框架扩展或替换框架组件。相比使用提供接口jar包,供第三方服务模块实现接口的方式,SPI的方式使得源框架,不必关心接口的实现类的路径文章来源地址https://www.toymoban.com/news/detail-403283.html

6.2 缺点

  • 虽然ServiceLoader也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。如果你并不想用某些实现类,它也被加载并实例化了,这就造成了浪费。获取某个实现类的方式不够灵活,只能通过Iterator形式获取,不能根据某个参数来获取对应的实现类
  • 多个并发多线程使用ServiceLoader类的实例是不安全的

到了这里,关于Java-SPI机制详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 相机专业模型详解,各个参数作用,专业模式英文全称和缩写

    ISO(感光度) 全称: International Organization for Standardization 缩写: ISO Shutter Speed(快门速度) 全称: Shutter Speed 缩写: SS Aperture(光圈大小) 全称: Aperture 缩写: A White Balance(白平衡) 全称: White Balance 缩写: WB Exposure Compensation(曝光补偿) 全称: Exposure Compensation 缩写:

    2024年02月03日
    浏览(38)
  • SPI机制是如何规避双亲委派机制的?

    双亲委派机制是什么? 双亲委派机制指的是Java中类加载机制的特性。 双亲委派机制是作用于什么地方? 双亲委派机制主要作用于类加载的时候。 类加载器 首先需要清晰的知道,双亲委派机制指的是类加载的特性。在了解其特性之前,我们需要先了解类加载器有哪些(不考虑

    2024年02月11日
    浏览(29)
  • Dubbo的SPI机制

    加载扩展点配置 :Dubbo SPI首先会加载所有的扩展点配置,这些配置通常是在META-INF/dubbo目录下的properties文件中定义的。每个配置文件的名称就是扩展点接口的全限定名,文件内容是扩展点实现的键值对,键是扩展点的名称,值是扩展点实现类的全限定名。 创建扩展点实例 :

    2024年01月23日
    浏览(42)
  • SPI 动态服务发现机制

    SPI(Service Provier Interface)是一种服务发现机制,通过ClassPath下的META—INF/services文件查找文件,自动加载文件中定义的类,再调用forName加载; spi可以很灵活的让接口和实现分离, 让API提供者只提供接口, 第三方来实现。 ​ 优点: 使用Java SPI机制的优势是实现解耦,使得第

    2024年01月20日
    浏览(23)
  • 如何实现SPI机制

    SPI(Service Provider Interface) 是一种面向接口编程的技术,它可以让一个程序根据接口约定规范自动发现和加载对应的实现类。它是一种 Java 种的接口编程规范,它定义了接口和服务提供者之间的约定规范,使得在运行时动态加载实现该接口的类。SPI 机制是通过在服务提供者接

    2024年02月05日
    浏览(26)
  • 【ZYNQ】SPI 简介及 EMIO 模拟 SPI 驱动示例

    SPI 是串行外设接口(Serial Peripheral Interface)的缩写,是美国摩托罗拉公司(Motorola)最先推出的一种同步串行传输规范,是一种高速、全双工、同步通信总线,可以在同一时间发送和接收数据,SPI没有定义速度限制,通常能达到甚至超过10M/bps。 SPI 有主、从两种模式,通常由

    2024年02月07日
    浏览(30)
  • Java反射机制深入详解

    一.概念 反射就是把Java的各种成分映射成相应的Java类。 Class类的构造方法是private,由JVM创建。 反射是java语言的一个特性,它允程序在运行时(注意不是编译的时候)来进行自我检查并且对内部的成员进行操作。例如它允许一个java的类获取他所有的成员变量和方法并且显示

    2024年02月06日
    浏览(32)
  • Java类加载机制详解

    类加载器 加载类 备注 启动类加载器(Bootstrap ClassLoader) JAVA_HOME/jre/lib 无上级,无法直接访问 由jvm加载 拓展类加载器(Extension ClassLoader) JAVA_HOME/jre/lib/ext 父加载器为 Bootstrap,显示为 null 。该类由Bootstrap加载 应用类加载器(Application ClassLoader) classpath 父加载器上级为 Exten

    2024年02月08日
    浏览(23)
  • Java-泛型机制详解

    Java集合(Collection)中元素的类型是多种多样的。例如,有些集合中的元素是Byte类型的,而有些则可能是String类型的,等等。Java允许程序员构建一个元素类型为Object的Collection,其中的元素可以是任何类型在[Java SE](https://baike.baidu.com/item/Java SE/4662159?fromModule=lemma_inlink) 1.5之前,

    2023年04月10日
    浏览(31)
  • Java的垃圾回收机制详解

    目录 1、C语言与Java语言垃圾回收区别 2、System.gc() 3、面试题引入Java垃圾回收 3.1 jvm怎么确定哪些对象应该进行回收 3.1.1 引用计数法 3.1.2 可达性分析算法  3.2 jvm会在什么时候进行垃圾回收的动作 3.2 jvm到底是怎么回收垃圾对象的 4、垃圾回收算法 4.1 标记-清除算法 4. 2 复制算

    2024年02月09日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包