目前因为数据库迁移导致的一系列问题,所以我这边阅读了一点点 mysql-connector-java 的源码
看到有这样的文件夹
了解了之后才知道是 java 的 spi 其实我感觉就是一种变相面向接口编程,解耦代码的方式
其实真的再你使用的时候你自己再注入
Class.forName("com.mysql.cj.jdbc.Driver");
就可以了
为什么还要设计这样一种文件夹格式,其实你还是手动要在配置文件指定这个 Driver
我不太能完全理解
1
blindpirate 2021-06-28 22:45:59 +08:00
不能理解就去读 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. |
2
fkdog 2021-06-28 22:48:09 +08:00
SPI 和 API 同样都有程序开发接口的意思。
只不过 SPI 一般表示我是接口的提供方 /实现方。 比如一些支付类的第三方应用需要回调我方某个接口,这个接口对我方来说是 SPI,对调用方来说是 API 。 具体到 java 这种语言里,你实现某个接口类、抽象类,也算是一种 SPI 。 java 语言提供接口标准,具体的实现交给第三方,以前的 JavaEE EJB 里到处都是这样的设计。 |
3
GuuJiang 2021-06-28 22:48:55 +08:00 via iPhone 4
Class.forName 是在用了 SPI 机制之前的加载技术,实际上当包加入了 SPI 元信息后并不需要再写 Class.forName 了,只不过天下文章一大抄,各种只知其然不知其所以然的“教程”仍然在把 Class.forName 这种已经过时的东西传来传去
|
4
0576coder OP |
5
0576coder OP |
6
fantastM 2021-06-29 01:49:37 +08:00 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 的思想不一样吗 |
7
0576coder OP @fantastM
这儿的「 JDK - JDBC 驱动 - 开发者」三个角色都被耦合了 我明白这个意思,因为 jdbc 都封好了接口,所以不管连什么数据库,接口都是基本一致的 开发者只需要面向接口编程 而不是面向实现编程,这个依赖注入就能解决掉。 我是不理解这种配置文件的方式,这个其实跟我手动注入,感觉本质上他没有很大的区别。 根据配置注入具体的实例=SPI 吗 那我感觉本质上也是一种依赖注入 不知道是不是可以这样理解 |
8
fantastM 2021-06-29 02:49:56 +08:00
> 我是不理解这种配置文件的方式,这个其实跟我手动注入,感觉本质上他没有很大的区别。
如果这个 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 还是不太一样的吧,虽然这两者的都是为了解耦。 楼主你纠结的「这种配置文件的方式」和「跟我手动注入」两种方式,代码跑起来是没什么区别,但你站高处想一想,两者从设计上有什么区别,尤其是对使用者而言。 |
9
lionseun 2021-06-29 07:38:30 +08:00 via Android
从设计模式的角度考虑,都有了 drivermanager 了,还搞什么 class fromname
|
10
BBCCBB 2021-06-29 08:34:16 +08:00
有了 spi, 一般情况下就不需要你手动指定 Class.forName("com.mysql.cj.jdbc.Driver") 了.
|
11
qwerthhusn 2021-06-29 08:54:14 +08:00
就像 Servlet 一样,Tomcat/Jetty 实现 SPI,开发者去用 API
|
12
szq8014 2021-06-29 10:10:15 +08:00 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 接口不是? |
13
0576coder OP 其实我有点明白了
这个 SPI 应该就是 JDK 自带的一种规范,顺便自带了代码层面的服务发现类似的功能,我只要按照 SPI 的规范去实现接口,他就能自动知道我这个包是 xx 接口的具体实现。所以使用的时候只需要配置文件指定下就行 而 DI 依赖注入只是一种代码层面的设计模式 他们的本质都是为了面向接口编程 解耦 |
14
MineDog 2021-06-29 10:37:03 +08:00
其实都是约定,有了约定,很多东西就可以标准化了
|
15
GuuJiang 2021-06-29 11:45:48 +08:00 via iPhone
@0576coder 是的,你在主题中的疑惑应该来自于“反正都要手写 Class.forName,那么 spi 就没意义了”,事实上只要认识到 Class.forName 不但是多余的,甚至根本就是错误的,正确的做法是只需要把带有元信息的相应的实现放到 classpath 即可,当需要替换驱动时对代码完全无感,从这个角度看 spi 跟多态、DI 、服务发现等都是一回事,手写 Class.forName 反而依赖了具体实现
|
16
Bromine0x23 2021-06-29 12:10:23 +08:00
从 2006 的 JDBC 4.0 规范开始就可以使用 SPI 自动加载了,除非用老古董驱动不然可以忘掉 Class.forName 了。
SPI 本身是一种实现注册机制,JDBC 用的 java.sql.Driver 是最常见的,其他还有 java.security.Provider ( JCE )、javax.servlet.ServletContainerInitializer 、javax.annotation.processing.Processor 之类的 |