介:
SPI的全名为Service Provider Interface。java 运行时服务发现机制。我们在系统开发过程中不同模块之间一般都是用接口进行通信的。接口一般都有多个实现类。如果在模块里编码的时候涉及到了具体的实现类,则后续需要修改实现类或者新增实现类的时候就要修改原来的模块编码,这样就违反了开闭原则(对扩展开发,对修改关闭。避免新增功能的时候修改原来的代码对原有的功能造成新的bug。)不利于代码的维护。JDK的SPI实现了为接口自动寻找实现类的功能。使用场景一般适合插件,框架上等。jdbc4,lombok等
JDK SPI
//接口类
public interface ISay { void say(String content); }
//实现类A
public class SayA implements ISay {
@Override
public void say(String content) {
System.out.println(content);
}
}
//实现类B
public class SayB implements ISay {
@Override
public void say(String content) {
System.out.println("==" + content + "==");
}
}
新增文件 路径:/resource/META-INF/services 。文件名称aaa.bbb.vvv.ISay。文件内容如下
aaa.bbb.vvv.SayB
aaa.bbb.vvv.SayA
测试方法:
public class JdkSpi {
/**
* 运行时执行指定service方法
*/
private void excu(String content){
ServiceLoader<ISay> serviceLoader = ServiceLoader.load(ISay.class);
Iterator<ISay> syas = serviceLoader.iterator();
while (syas.hasNext()) {
ISay say = syas.next();
say.say(content);
}
}
public static void main(String[] args) {
new JdkSpi().excu("123456");
}
得到结果。运行了SayA。的方法,也运行了SayB的方法。
原理:
接下来从代码层面看看SPI都为我们做了什么。首先看看java.util.ServiceLoader的实现。我们看到ServiceLoader使用非常简单,只需要调用一个静态方法load并以要加载的服务的父类(通常是一个interface或abstract class)作为参数,jvm就会帮我们构建好当前进程中所有注册到 META-INF/services/[service full qualified class name] 的服务。
。
举例项目中的ServiceLoader的存在
JDBC中使用案例解读
public class DriverManager {
//中间略
/**
* 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;
}
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
//为何只是执行了next,没有其他操作呢。后面讲解
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers);
//这段其实是另外的加载方式,与方法第一行呼应
if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
//mysql驱动
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
// 静态代码块实现了驱动注册到jdbc。再ServiceLoad.load会执行对应Class的Class.forname。这个时候会执行。
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
Connection conn = null;
Statement stmt = null;
try{
// 注册 JDBC driver jdbc4.0之后就没有必要了.因为DriverManagger类加载时候会去用ServiceLoad执行了这个步骤,并且只加载一次。
Class.forName("com.mysql.jdbc.Driver");
// 打开连接
conn = DriverManagger.getConnection(DB_URL,USER,PASSWD);
// 执行一条sql
stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
}
Dubbo SPI扩展
dubbo是个rpc框架,做了很好的rpc机制,多比jdk的rpc做了优化,如IOC依赖注入(JDK是需要提供空构造方法的),使用的时候加载(JDK会把所有配置的实现类加载,dubbo只加载使用到的),name的扩展等。
我们可以根据需要扩展,如缓存,线程池等,对dubbo进行扩展优化。
guava缓存针对dubbo扩展优化。。。
dubbo扩展后续再说