JDK之SPI机制
个人理解
- SPI就是一种约定机制,通常情况下, 我们服务都会有一个接口和一个实现类,当服务提供者提供某个服务时,需要在/META-INF/services/目录下创建以接口路径命名的文
- 在这个文件中, 指定我们实现的服务com.demo.impl.UserServiceImpl;
package com.tuling;
public interface Product {
void use();
}
public class Phone implements Product{
@Override
public void use() {
System.out.println("call");
}
}
package com.tuling;
public class Food implements Product{
@Override
public void use() {
System.out.println("eat");
}
}
在/META-INF/services/创建一个com.demo.UserService文件
com.tuling.Phone
com.tuling.Food
JDK 的SPI调用机制
ServiceLoader<Product> productServiceLoader = ServiceLoader.load(Product.class);
Iterator<Product> iterator = productServiceLoader.iterator();
while (iterator.hasNext()){
Product product = iterator.next();
product.use();
}
运行结果:
call
eat
- ServiceLoader是JDK自带实现的SPI扫描类, 传入接口类Class信息, 然后JDK会根据接口的路径信息去扫描/META-INF/services/com.tuling.Product文件
- 然后将文件里面的内容一行一行全部读取出来,使用反射技术,将指定的实现类Phone,Food通过反射实例化, 放入某种数据结构容器中;
通过上面的简单例子, 差不多就可以理解SPI是个什么东西。
SPI机制的作用
步入正题, 那么JDK提供SPI机制的作用是什么呢?
- 解耦
- 与实现组件化,模块化;
- 可以提供一套接口规范, 每个依赖包提供接口实现类,然后在/WEB-INF/services下的以接口路径命令的文件里, 指定包的实现类,项目启动的时候, 就会去扫描/WEB-INF/services下的文件,一起使用;
SPI机制常见举例
举个例子: 我们开发使用的DB操作, 一般会有Mysql, Oracle等,那么JDBC中, 有一个DriverManger类, 用来获取数据库连接;
JDBC提供了一个java.sql.Driver 接口,当我们使用不同的数据库时,各大厂商(如Mysql、Oracle)会根据一个统一的规范(java.sql.Driver)开发各自的驱动实现逻辑,客户端使用jdbc时不需要去改变代码,mysql依赖包中 /WEB-INF/services/有一个java.sql.Driver文件, 内容指定了Driver的实现类是com.mysql.jdbc.Driver, oracle依赖包中/WEB-INF/services/也有一个java.sql.Driver,内容指定了Driver的实现类是oracle.jdbc.driver.OracleDriver;
package java.sql;
import java.util.Iterator;
import java.util.ServiceLoader;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.concurrent.CopyOnWriteArrayList;
import sun.reflect.CallerSensitive;
import sun.reflect.Reflection;
public class DriverManager {
@CallerSensitive
public static Connection getConnection(String url,
java.util.Properties info) throws SQLException {
return (getConnection(url, info, Reflection.getCallerClass()));
}
//...
}
// Worker method called by the public getConnection() methods.
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
// synchronize loading of the correct classloader.
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
if(url == null) {
throw new SQLException("The url cannot be null", "08001");
}
for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
}
for(DriverInfo aDriver : registeredDrivers) 循环中的registeredDrivers代表的就是所有的Driver实现类,再根据每个驱动Driver实现类去连接具体的数据库;
而我们项目中可以一次引入mysql,oracle等多种数据库, 只需要在Mybatis的配置文件中指定使用哪种数据库, 进而再获取连接的时候,创建相关数据库的连接;
Java SPI机制的特点
优点:
- 插件化
- 模块化
缺点:
- 不够灵活;
只可以获取所有的 规范实现, 做不到只获取部分规范实现;
项目可能使用10种数据库,就需要导入10种数据库包,我们短时间内只用到了3种 他就会将10种数据库的规范实现Driver全部扫描进来, 而不能只获取3种;
总结
Dubbo的SPI扩展机制就是对Java SPI机制的扩展, 使其更加灵活可用;