前言
SPI全称叫Service Provider Interface,在Java 6时引入的这个功能,用于加载所给出的接口相匹配的实现, 就是可以在运行时动态给一个接口添加实现,只需要在src/META-INF/services/
目录下建立一个文件,文件名是接口的全限定名,文件的内容是由多行具体的实现类全名组成。
简单的说SPI就是为某个接口寻找实现的机制,他的核心便是解耦合,通过SPI机制,将实现类隐藏在接口后面,根据需要寻找服务实现。
有不少框架用它来做服务的扩展发现,如果阅读过一些框架的源码,就会发现它会无处不在,比如我们经常使用的spring框架,spring-web包下就使用这个机制。
Effective Java中也提到SPI是一种将服务接口与服务实现分离以达到解耦、大大提升了程序可扩展性的机制。引入服务提供者就是引入了SPI接口的实现者,通过本地的注册发现获取到具体的实现类,轻松可插拔。
SPI示例
首先定义一个接口,并写一个或多个接口的实现类。
public interface IPrint {
public void print(String msg);
}
简单打印:
public class BasePrint implements IPrint {
@Override
public void print(String msg) {
System.out.println(msg);
}
}
打印调用者的类名、方法名:
public class SeniorPrint implements IPrint {
@Override
public void print(String msg) {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
StackTraceElement stackTraceElement = stackTrace[stackTrace.length - 1];
System.out.println(String.join("===>",stackTraceElement.getClassName(),stackTraceElement.getMethodName(),msg));
}
}
新建META-INF/services
文件夹,在其中创建这个接口的全限定名文件,文件中是其接口的一个或多个实现类。
最后通过ServiceLoader加载出所有IPrint的实现类。
public class SpiMain {
public static void main(String[] args) {
ServiceLoader<IPrint> shouts = ServiceLoader.load(IPrint.class);
IPrint print =null;
for (IPrint item : shouts) {
print=item;
}
if (print!=null){
print.print("demo");
}
}
}
输出:
com.spi.SpiMain===>main===>demo
JDBC中是如何使用的?
在以前,我们通常会先使用Class.forName("com.mysql.cj.jdbc.Driver")
加载数据库的驱动,然后再进行获取连接,而在后续的版本中不需要用Class.forName(“com.mysql.jdbc.Driver”)来加载驱动,直接获取连接就可以了,这种方式就是使用了SPI扩展机制,(具体在那个版本中,我并没有去查阅)。
public class SpiMain {
public static void main(String[] args) {
try {
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/db_school", "root", "hxl495594..");
System.out.println(connection);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
输出:
com.mysql.cj.jdbc.ConnectionImpl@281e3708
来看一下具体的源码,首先是DriverManager的静态代码块,调用了loadInitialDrivers方法,用来加载驱动。
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
在loadInitialDrivers方法下就可以看出,使用ServiceLoader.load(Driver.class)
加载Driver的实现类,然后通过 driversIterator.next();
加载其实现类。
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
}
return null;
}
});
这里的Iterator其实是ServiceLoader内部类下的LazyIterator实例,通过层层调用,最终会调用Class.forName
。
public S next() {
if (acc == null) {
return nextService();
} else {
PrivilegedAction<S> action = new PrivilegedAction<S>() {
public S run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
而在mysql的jar包下必定会存有java.sql.Driver文件,内容是就是其实现类。
类加载后会自动执行静态方法,调用DriverManager类的注册方法registeredDrivers向集合中加入实例,之后我们就可以使用DriverManager.getConnection
获取了。
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}