V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
0576coder
V2EX  ›  Java

关于 Java 的 SPI,我感觉就是一个面向接口编程的概念,为何要这样设计

  •  
  •   0576coder · Jun 28, 2021 · 3620 views
    This topic created in 1773 days ago, the information mentioned may be changed or developed.

    目前因为数据库迁移导致的一系列问题,所以我这边阅读了一点点 mysql-connector-java 的源码

    看到有这样的文件夹

    了解了之后才知道是 java 的 spi 其实我感觉就是一种变相面向接口编程,解耦代码的方式

    其实真的再你使用的时候你自己再注入

    Class.forName("com.mysql.cj.jdbc.Driver");
    

    就可以了

    为什么还要设计这样一种文件夹格式,其实你还是手动要在配置文件指定这个 Driver

    我不太能完全理解

    16 replies    2021-06-29 12:10:23 +08:00
    blindpirate
        1
    blindpirate  
       Jun 28, 2021
    不能理解就去读 ServiceLoader 的煮食,里面说的很清楚:

    > A service provider that is packaged as a JAR file for the class path is
    > identified by placing a <i>provider-configuration file</i> in the resource
    > directory {@code META-INF/services}. The name of the provider-configuration
    > file is the fully qualified binary name of the service. The provider-configuration
    > file contains a list of fully qualified binary names of service providers, one
    > per line.
    fkdog
        2
    fkdog  
       Jun 28, 2021
    SPI 和 API 同样都有程序开发接口的意思。
    只不过 SPI 一般表示我是接口的提供方 /实现方。
    比如一些支付类的第三方应用需要回调我方某个接口,这个接口对我方来说是 SPI,对调用方来说是 API 。
    具体到 java 这种语言里,你实现某个接口类、抽象类,也算是一种 SPI 。
    java 语言提供接口标准,具体的实现交给第三方,以前的 JavaEE EJB 里到处都是这样的设计。
    GuuJiang
        3
    GuuJiang  
       Jun 28, 2021 via iPhone   ❤️ 4
    Class.forName 是在用了 SPI 机制之前的加载技术,实际上当包加入了 SPI 元信息后并不需要再写 Class.forName 了,只不过天下文章一大抄,各种只知其然不知其所以然的“教程”仍然在把 Class.forName 这种已经过时的东西传来传去
    0576coder
        4
    0576coder  
    OP
       Jun 28, 2021
    @blindpirate
    我知道 这其实就是一个规范
    比如我也手写了实现了 jdbc 接口的包,我直接 new driver 的时候注入我这个实现类就好了 为什么要有这样的一个文件夹,感觉多此一举
    0576coder
        5
    0576coder  
    OP
       Jun 28, 2021
    @fkdog
    我手动这样实现接口 然后再具体使用的时候才注入具体的类 这种不应该叫 DI 么 依赖注入

    这个 SPI 是不是类似的意思 只不过多了个文件夹- -
    fantastM
        6
    fantastM  
       Jun 29, 2021 via iPhone
    如果你的应用需要连接多个不同的数据源( MySQL 、PostgreSQL 、Oracle…),那么就需要使用多个 JDBC 的驱动。按你说的方式,开发者需要先去各个驱动对应的官网查资料(得知道 com.mysql.cj.jdbc.Driver 这个约定值),然后再编写多次各个驱动对应的注册代码。这儿的「 JDK - JDBC 驱动 - 开发者」三个角色都被耦合了。

    用 SPI 这种机制的话,起码在注册驱动这一点上,开发者是不用再顾虑了的,JDBC 驱动可以在其内部提供实现。楼主可能对 MySQL 已经很熟悉了,所以体会不是很深,不过假设现在要连接一个你完全没接触过的数据库(例如 SQLite ),你是不是会期望做的事情越少越好?

    还有你说的 DI 什么的……那更好理解了,你看看一个单纯的 Spring 应用和基于 Spring Boot 应用有什么区别,然后 Spring Boot 是怎样提供一些默认配置的,它的 spring.factories 文件有什么作用?这难道和 SPI 的思想不一样吗
    0576coder
        7
    0576coder  
    OP
       Jun 29, 2021
    @fantastM
    这儿的「 JDK - JDBC 驱动 - 开发者」三个角色都被耦合了
    我明白这个意思,因为 jdbc 都封好了接口,所以不管连什么数据库,接口都是基本一致的 开发者只需要面向接口编程 而不是面向实现编程,这个依赖注入就能解决掉。
    我是不理解这种配置文件的方式,这个其实跟我手动注入,感觉本质上他没有很大的区别。
    根据配置注入具体的实例=SPI 吗 那我感觉本质上也是一种依赖注入 不知道是不是可以这样理解
    fantastM
        8
    fantastM  
       Jun 29, 2021
    > 我是不理解这种配置文件的方式,这个其实跟我手动注入,感觉本质上他没有很大的区别。

    如果这个 JDBC 的 SPI 配置文件是你写的,那相当于你是 JDBC 驱动的开发者了,这样的话,确实和你手动注入没什么区别,毕竟工作量都是你一个人的......

    > 根据配置注入具体的实例=SPI 吗 那我感觉本质上也是一种依赖注入 不知道是不是可以这样理解

    SPI 就是 Service Provider Interface 的缩写,用「根据配置注入具体的实例」拿来做可扩展的服务发现,是一种解耦思想的体现。

    例如,SDK ( JDK )提供约定行为的 Interface ( java.sql.Driver),并且对这个 Interface 使用逻辑还是在 SDK 里的(在 java.sql.DriverManager#getConnection(String url) 里会用到),然后 SPI 的实现者( mysql-connector-java )只需提供 Interface 的具体实现( com.mysql.cj.jdbc.Driver )即可,不需要关心 Interface 的使用逻辑。

    从这方面看,SPI 和 DI 还是不太一样的吧,虽然这两者的都是为了解耦。

    楼主你纠结的「这种配置文件的方式」和「跟我手动注入」两种方式,代码跑起来是没什么区别,但你站高处想一想,两者从设计上有什么区别,尤其是对使用者而言。
    lionseun
        9
    lionseun  
       Jun 29, 2021 via Android
    从设计模式的角度考虑,都有了 drivermanager 了,还搞什么 class fromname
    BBCCBB
        10
    BBCCBB  
       Jun 29, 2021
    有了 spi, 一般情况下就不需要你手动指定 Class.forName("com.mysql.cj.jdbc.Driver") 了.
    qwerthhusn
        11
    qwerthhusn  
       Jun 29, 2021
    就像 Servlet 一样,Tomcat/Jetty 实现 SPI,开发者去用 API
    szq8014
        12
    szq8014  
       Jun 29, 2021   ❤️ 1
    > 为什么还要设计这样一种文件夹格式,其实你还是手动要在配置文件指定这个 Driver

    用了 SPI 就不再需要手动指定了,#3 @GuuJiang 也说了,Class.forName 至少是在用了 SPI 规范后是不需要了。


    SPI 就是把接口的具体实现给隔离了,
    就像 Spring 里面各种 interface 与 xxImpl 一样。

    也像#11 楼 @qwerthhusn 说的那样,你照着 Servlet 规范写了一个 web 就可以部署在 Tomcat, Jetty, WildFly 等等一样,你用 javax.servlet.http.HttpServletRequest 这个接口就可以,一般情况下不用关心是谁来实现的这个东西,你`代码`里面也不用去显式的声明我依赖 Tomcat 我依赖 Jetty 。


    > 这个其实跟我手动注入,感觉本质上他没有很大的区别

    那是,大家都在解决如何动态的加载一个接口实现,只不过 SPI 把这块抽取出来成了一个规范而已。
    你没了 javax.servlet.api 照样能写 web 接口不是?
    0576coder
        13
    0576coder  
    OP
       Jun 29, 2021
    其实我有点明白了
    这个 SPI 应该就是 JDK 自带的一种规范,顺便自带了代码层面的服务发现类似的功能,我只要按照 SPI 的规范去实现接口,他就能自动知道我这个包是 xx 接口的具体实现。所以使用的时候只需要配置文件指定下就行

    而 DI 依赖注入只是一种代码层面的设计模式

    他们的本质都是为了面向接口编程 解耦
    MineDog
        14
    MineDog  
       Jun 29, 2021
    其实都是约定,有了约定,很多东西就可以标准化了
    GuuJiang
        15
    GuuJiang  
       Jun 29, 2021 via iPhone
    @0576coder 是的,你在主题中的疑惑应该来自于“反正都要手写 Class.forName,那么 spi 就没意义了”,事实上只要认识到 Class.forName 不但是多余的,甚至根本就是错误的,正确的做法是只需要把带有元信息的相应的实现放到 classpath 即可,当需要替换驱动时对代码完全无感,从这个角度看 spi 跟多态、DI 、服务发现等都是一回事,手写 Class.forName 反而依赖了具体实现
    Bromine0x23
        16
    Bromine0x23  
       Jun 29, 2021
    从 2006 的 JDBC 4.0 规范开始就可以使用 SPI 自动加载了,除非用老古董驱动不然可以忘掉 Class.forName 了。
    SPI 本身是一种实现注册机制,JDBC 用的 java.sql.Driver 是最常见的,其他还有 java.security.Provider ( JCE )、javax.servlet.ServletContainerInitializer 、javax.annotation.processing.Processor 之类的
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   947 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 58ms · UTC 20:06 · PVG 04:06 · LAX 13:06 · JFK 16:06
    ♥ Do have faith in what you're doing.