@Otokaze
2018-12-05T03:31:36.000000Z
字数 1978
阅读 591
Java
Java 6 提供了一个 java.util.ServiceLoader 类,用于实现 Service Provider Interface(SPI)。
SPI 是什么东西呢?以 JDBC 为例子,JDBC 分为两部分,一个是 JDBC API,由 JDK 提供;另一个是 JDBC 驱动实现,由数据库厂商提供。JDBC API 提供了一个数据库驱动接口,java.sql.Driver,Driver 接口的具体实现类则位于不同的数据库厂商 JDBC 驱动中,如 MySQL 的 JDBC 驱动实现类为:com.mysql.jdbc.Driver。
当我们使用 JDBC 访问数据库时,通常的步骤为:
Class.forName("com.mysql.jdbc.Driver"),注册对应的 JDBC 驱动;DriverManager.getConnection(url, user, pass),获取数据库连接。注意,因为 Class.forName() 中指定了对应的 Driver 实现类的全限定类名,即驱动实现类的类名硬编码在我们的应用程序中,如果后期我们需要更换 Driver 实现类,比如换为 Oracle 的驱动,那么就需要修改程序的代码,这不符合开闭原则。为了解耦,JDK1.6 提供了 SPI 机制,JDBC 4.0 规范开始支持 SPI 机制,这样就不需要在程序中编写 Class.forName() 代码了。
那么 JDBC 4.0 是如何知道要使用什么 java.sql.Driver 实现类呢?很简单,JDK 规定,如果需要使用 SPI 机制,只需要在 ClassPath 路径中的 META-INF/services/java.sql.Driver 文件中放入具体的 java.sql.Driver 实现类的全限定类名就可以了。注意前缀为 META-INF/services/,而 java.sql.Driver 是文件名(UTF-8 编码的文本文件),文件名是有讲究的,即对应的接口名称,而文件内容是按行分割的,每行都是一个对于接口的实现类的全限定类名,比如 com.mysql.jdbc.Driver。
JDBC 4.0 在初始化时,会寻找 classpath 中的 META-INF/services/java.sql.Driver 文件(可以有多个,会自动合并在一起),然后实例化这些驱动类,然后我们在调用 DriverManager.getConnection() 方法时,JDBC 4.0 会自动选择合适的驱动实现类(具体如何选择请自行查看 JDBC 4.0 规范以及源码)。
接口:com.zfl9.service.Service:
package com.zfl9.service;public interface Service {void doService();}
实现类:com.zfl9.service.impl.HelloService:
package com.zfl9.service.impl;import com.zfl9.service.Service;public class HelloService implements Service {@Overridepublic void doService() {System.out.println("Hello Service");}}
实现类:com.zfl9.service.impl.WorldService:
package com.zfl9.service.impl;import com.zfl9.service.Service;public class WorldService implements Service {@Overridepublic void doService() {System.out.println("World Service");}}
SPI 文件:META-INF/services/com.zfl9.service.Service:
com.zfl9.service.impl.HelloServicecom.zfl9.service.impl.WorldService
Main 类:com.zfl9.service.ServiceMain:
package com.zfl9.service;import java.util.ServiceLoader;public class ServiceMain {public static void main(String[] args) {ServiceLoader<Service> serviceLoader = ServiceLoader.load(Service.class);for (Service service : serviceLoader) {service.doService();}}}
运行结果:
Hello ServiceWorld Service