[java]到底什么是spi?(代码片段)

削尖的螺丝刀 削尖的螺丝刀     2023-01-12     117

关键词:

           

        昨天在和我的小伙伴探讨 Druid 源码的时候,他提出了一个问题,问题是这样描述的:DruidDataSource#getConnection 中的 init 执行 DruidDriver.getInstance 的时候是如何把其他驱动注册的,也就是下面这里:

 public void init() throws SQLException 
        if (inited) 
            return;
        

        // bug fixed for dead lock, for issue #2980
        DruidDriver.getInstance();

        到底是为什么呢? 那么我们就以这个问题作为钥匙,开启我们对SPI学习的大门。 先跟进去看一下  ( 确实如果不是有人提醒我根本没在意,这里面有几个关键的静态代码块 )

   public static DruidDriver getInstance() 
        return instance;
    

DruidDriver的静态代码块:

static 
        AccessController.doPrivileged(new PrivilegedAction<Object>() 
            @Override
            public Object run() 
                registerDriver(instance);
                return null;
            
        );
    

  public static boolean registerDriver(Driver driver) 
        try 
            DriverManager.registerDriver(driver);

            try 
                MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();

                ObjectName objectName = new ObjectName(MBEAN_NAME);
                if (!mbeanServer.isRegistered(objectName)) 
                    mbeanServer.registerMBean(instance, objectName);
                
             catch (Throwable ex) 
                if (LOG == null) 
                    LOG = LogFactory.getLog(DruidDriver.class);
                
                LOG.warn("register druid-driver mbean error", ex);
            

            return true;
         catch (Exception e) 
            if (LOG == null) 
                LOG = LogFactory.getLog(DruidDriver.class);
            
            
            LOG.error("registerDriver error", e);
        

        return false;
    

        再跟进看一下 DriverManager.registerDriver(driver) 的内部也有个静态代码块:

  /**
     * Load the initial JDBC drivers by checking the System property
     * jdbc.properties and then use the @code ServiceLoader mechanism
     */
    static 
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    

  private static void loadInitialDrivers() 
        String drivers;
        try 
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() 
                public String run() 
                    return System.getProperty("jdbc.drivers");
                
            );
         catch (Exception ex) 
            drivers = null;
        
        // If the driver is packaged as a Service Provider, load it.
        // Get all the drivers through the classloader
        // exposed as a java.sql.Driver.class service.
        // ServiceLoader.load() replaces the sun.misc.Providers()

        AccessController.doPrivileged(new PrivilegedAction<Void>() 
            public Void run() 

                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();

                /* Load these drivers, so that they can be instantiated.
                 * It may be the case that the driver class may not be there
                 * i.e. there may be a packaged driver with the service class
                 * as implementation of java.sql.Driver but the actual class
                 * may be missing. In that case a java.util.ServiceConfigurationError
                 * will be thrown at runtime by the VM trying to locate
                 * and load the service.
                 *
                 * Adding a try catch block to catch those runtime errors
                 * if driver not available in classpath but it's
                 * packaged as service and that service is there in classpath.
                 */
                try
                    while(driversIterator.hasNext()) 
                        driversIterator.next();
                    
                 catch(Throwable t) 
                // Do nothing
                
                return null;
            
        );

可以看到关键方法:

 ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();

        

        我们都知道静态代码块是执行在最前面,而这里对其他驱动的加载正是在静态代码块里实现的,可是问题是为什么其他的驱动也会被加载呢?

       这 一切都要从Java类的加载机制 以及 这个问题的最关键原理 —— SPI 说起,而它通过驱动加载的方式,也算是打破类在加载过程中双亲委派机制 的一个关键手段 !

(  如果你要问为什么要打破? 原因就在于驱动属于Java核心类库,而核心类库的 Class文件 只能由启动类加载器加载,如果对加载过程和双亲委派原理掌握的不是很透彻的同学,可以通过这篇文章了解一下 : [ Java ] 一文说透所谓的双亲委派  )

那么 SPI 到底是个什么东西呢 ? 又为什么要有它 ?

说到底,最关键的就为了两个字 —— 解耦

如果你一定让我多说两个字,那就是为了方便在不改动原始代码的基础上 ——  扩展

        我们仔细想想,如果你是一个厂家,你生产了一个 jar 包给别人用,但是里面有些方法你觉得可以让别人自定义,但是又不想修改 jar 包怎么办? 这就是 SPI 的关键作用。他可以不通过修改原厂jar包实现客户自己定义关键方法。

        再来解释下 SPI , 它的全称是: Service Provider Interface , 是一种面向接口变成的思想规范,它是Java提供的一套用来被第三方实现或者扩展的接口,它可以用来启用框架扩展和替换组件。 SPI的作用就是为这些被扩展的API寻找服务实现。

  • API (Application Programming Interface)在大多数情况下,都是实现方制定接口并完成对接口的实现,调用方仅仅依赖接口调用,且无权选择不同实现。 从使用人员上来说,API 直接被应用开发人员使用。

  • SPI (Service Provider Interface)是调用方来制定接口规范,提供给外部来实现,调用方在调用时则选择自己需要的外部实现。 从使用人员上来说,SPI 被框架扩展人员使用。


有了上面的理论基础,我们这里写一个 Demo 来自己实现一套 SPI 看看:

1.首先我们建立一个普通Maven项目,模拟自己是一个SDK的提供者,我们再里面提供一个可以自定义的SPI接口

2.然后我们建立一个该接口的实现类

3.接下来我们建立一个测试工具,这个工具就模拟了SDK要提供服务的整理流程,中间夹杂了自定义方法:

4.指定 SPI 加载类

        敲黑板,关键点来了: SPI 的规范就是 ,要在资源包下 建立一个 META-INF.services 的文件夹,然后再这个文件夹里建立一个文件,千万注意的是,文件名必须是你要提供的服务类的全限定类名:那么我这里就是 spi.SPIService ,然后再这个文件里,写入你对这个服务实现类的全限定类名。

5.那么我们最终建立的内容结构就是这个样子了:


6.我们给这个项目 打成一个 jar 包,然后再另外创建一个maven项目来模拟我们的真实项目,然后引入刚才的jar包。

7.接下来我们建立一个SPIUser类,来加载并使用这个jar包提供的服务。

8.执行结果,可以看到我们jar包提供的服务生效了,且执行了默认的方法。

9.这个时候我们再运用 SPI 的机制,自定义一套方法,逻辑和上面一样,只不过是在当前项目中完成,相当于”重写“ 的概念:

10.同样,定义SPI路径

11.再次 执行刚才的方法,可以看到结果变了:

上面完整描述了 SPI 的实现过程 , 这会我们再来看看 Druid 源码中的内容,同样是不是也看到了 SPI 的身影 :

        至此,我们对 SPI 机制有了一个整体的了解,而他的好处,我刚才也说了,关键就在解耦,概括起来如下:

  • 不需要改动源码就可以实现扩展,解耦。
  • 实现扩展对原来的代码几乎没有侵入性。
  • 只需要添加配置就可以实现扩展,符合开闭原则。

分布式的几件小事dubbo的spi思想是什么(代码片段)

1.什么是SPI机制SPI全称为ServiceProviderInterface,是一种服务发现机制。SPI的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我... 查看详情

学习java中的spi机制(代码片段)

Java-SPI是什么SPI的全称是ServiceProviderInterface,是Java提供的一套用来被第三方实现或者扩展的API,SPI的作用就是为这些被扩展的API寻找服务实现。这是网上的一张图:JavaSPI实际上是“接口+策略模式+配置文件”实... 查看详情

servlet到底是什么?(代码片段)

servlet到底是什么?对于这个问题一直云里雾里的,今天打算刨根问底。一、Servlet简介  Servlet是sun公司提供的一门用于开发动态web资源的技术。  Sun公司在其API中提供了一个servlet接口,用户若想用发... 查看详情

spi总线协议(代码片段)

...119,觉得写的很通俗易懂,请给原作者点赞。文章目录什么是SPISPI主从模式SPI信号线SPI设备选择SPI数据发送接收SPI通信的四种模式SPI的通信协议SPI的三种模式SPI原理图连接STM32中SPI初始化配置什么是SPISPI是英语SerialPeri 查看详情

华为二面!!!面试官直接问我java中到底什么是nio?这不是直接送分题???(代码片段)

华为二面!!!面试官直接问我Java中到底什么是NIO?这不是直接送分题???什么是NIO缓冲区(Buffer)缓冲区类型获取缓冲区核心属性核心方法非直接缓冲区和直接缓冲区非直接缓冲区直接缓冲区通道(Cha... 查看详情

作为一个java工程师,你应该要知道spi机制(代码片段)

什么是SPISPI是ServiceProviderInterface的简称,是JDK默认提供的一种将接口和实现类进行分离的机制。这种机制能将接口和实现进行解耦,大大提升系统的可扩展性。SPI机制约定:当一个Jar包需要提供一个接口的实现类时,这个Jar包需... 查看详情

dubbo源码学习详解dubbo里的spi(代码片段)

目录一、SPI是什么?二、SPI的具体使用Services里的SPIDubbo.internal里的SPI三、@SPI注解使用四、小结一、SPI是什么?        SPI是中间件设计不可缺少的一项,SPI能提高应用的可插拔性,SPI的英文全称:ServiceProviderImplementatio... 查看详情

dubbo源码学习详解dubbo里的spi(代码片段)

目录一、SPI是什么?二、SPI的具体使用Services里的SPIDubbo.internal里的SPI三、@SPI注解使用四、小结一、SPI是什么?        SPI是中间件设计不可缺少的一项,SPI能提高应用的可插拔性,SPI的英文全称:ServiceProviderImplementatio... 查看详情

多线程start和run方法到底有什么区别?(代码片段)

昨天栈长介绍了《Java多线程可以分组,还能这样玩!》线程分组的妙用。今天,栈长会详细介绍Java中的多线程start()和run()两个方法,Java老司机请跳过,新手或者对这两个不是很理解的可以继续往下看。首先要知道实现多线程最... 查看详情

springboot到底是什么?(代码片段)

...始搭建以及开发过程。本文分享自华为云社区《SpringBoot到底是什么?如何理解parent、starter、引导类以及内嵌Tomcat?》,作者:我是一棵卷心菜。SpringBoot是由Pivotal团队提 查看详情

java中synchronized和lock的底层实现到底有什么区别(代码片段)

【1】synchronizedsynchronized通过monitor和mutexlock实现了互斥synchronized是通过每个对象与之相关的一个叫做监视器(monitor)来实现的监视器内有个组件是计数器,默认值为0,当计数器为0,尝试加锁,加锁成功... 查看详情

java中synchronized和lock的底层实现到底有什么区别(代码片段)

【1】synchronizedsynchronized通过monitor和mutexlock实现了互斥synchronized是通过每个对象与之相关的一个叫做监视器(monitor)来实现的监视器内有个组件是计数器,默认值为0,当计数器为0,尝试加锁,加锁成功... 查看详情

python中的logger和handler到底是个什么鬼(代码片段)

最近的任务经常涉及到日志的记录,特意去又学了一遍logging的记录方法。跟java一样,python的日志记录也是比较繁琐的一件事,在写一条记录之前,要写好多东西。典型的日志记录的步骤是这样的:创建logger创建handler定义formatter... 查看详情

restful架构到底是什么?(代码片段)

1导读1.1来源REST这个词,是RoyThomasFielding在他2000年的博士论文中提出的。他参与设计了HTTP协议,也是ApacheWebServer项目(可惜现在已经是Nginx的天下)的co-founder。论文地址:ArchitecturalStylesandtheDesignofNetwork-basedSoftwareA 查看详情

学习手写dubbo中的spi(代码片段)

Dubbo的SPI是什么SPI的全称是ServiceProviderInterface,是Java提供的一套用来被第三方实现或者扩展的API,SPI的作用就是为这些被扩展的API寻找服务实现。Dubbo核心特点SPI机制(按需加载)自适应扩展点IOC和AOPDubbo怎么和Spring集成Dubbo... 查看详情

fpga--spi通信(代码片段)

一,SPI说明:1、什么是SPI?SPI是串行外设接口(SerialPeripheralInterface)的缩写。是Motorola公司推出的一种同步串行接口技术,是一种高速的,全双工,同步的通信总线。2、SPI优点支持全双工通信、通信简单、数据传输速率块3、缺点... 查看详情

dubbo源码学习详解dubbo里的spi(代码片段)

目录一、SPI是什么?二、SPI的具体使用Services里的SPIDubbo.internal里的SPI三、@SPI注解使用四、小结一、SPI是什么?        SPI是中间件设计不可缺少的一项,SPI能提高应用的可插拔性,SPI的英文全称:ServiceProviderImplementatio... 查看详情

r语言中的factor到底是什么?(代码片段)

R语言中的Factor到底是什么?  因子(factors)是用于对数据进行分类(categorize)并将其存储为不同水平或者级别(levels)的数据对象。它们既可以存储字符串,也可以存储整数。Factors的唯一值是有限的。像“男”、“女”和“真”... 查看详情