例子:
IOperation plus = new PlusOperationImpl();
IOperation division = new DivisionOperationImpl();
System.out.println(plus.operation(6, 3));//加法
System.out.println(division.operation(6, 3));//除法
通常我们要定义一个四则运算接口IOperation,然后会写他的实现类PlusOperationImpl,DivisionOperationImpl。
然后在各自的实现类中先实现接口,实现相应的方法。
但是,java设计来一波很sao的操作。
再看另外一个例子:
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/test";
String username = "root";
String password = "123";
Connection conn = DriverManager.getConnection(url,username,password);
上面是jdbc获取连接的代码,很平常。但是我们不好奇它是怎么实现的吗?
首先是第一行加载类,所以看看com.mysql.jdbc.Drver
package com.mysql.jdbc;
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
他有一个静态代码块,我们都知道类在加载的时候是会执行静态代码块的,他new了一个Driver对象,术语叫注册驱动,说人话就是把新建的对象放到内存中去了后面来使用。最终它会存到java.sql.DriverManager类中的CopyOnWriteArrayList<DriverInfo> registeredDrivers属性里。后面会迭代这个list。
第一步事情做完了,下面就是DriverManager.getConnection这个方法获取连接了,
@CallerSensitive
public static Connection getConnection(String url,
String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();
if (user != null) {
info.put("user", user);
}
if (password != null) {
info.put("password", password);
}
return (getConnection(url, info, Reflection.getCallerClass()));
}
然后又调用getConnection方法:
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
......//
for(DriverInfo aDriver : registeredDrivers) {
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
......///
}
去掉一些,只看关键部分 Connection con = aDriver.driver.connect(url, info);这个才是获取连接的
然后你会发现,他丫的aDriver.driver是个接口,然鹅它就这么调用了。真是会玩,接口也能调。
当然接口肯定不能直接拿来用,上面肯定藏着它的实现类,点进DriverInfo你会发现它它好像内部类,因为它所在文件的类名叫java.sql.DriverManager,仔细看才发现他丫的公用了一个文件才不是什么内部类,也就是只要是同一个包里面就可以new了。
如果你在这行打个断点,debug的时候会发现aDriver.driver不是接口了,已经被初始化了。
原理就是这个类,java.util.ServiceLoader。你要问我,我怎么知道是在这个类里面。秘诀就是debug,driver的初始化时在这个类DriverInfo里面,而这个类就一个构造方法。咱就在这个构造方法打个断点,果然执行了,然后你在一步步debug(真是很晕,你会N多个类)
最终的最终你会到达ServiceLoader它定义了一个变量
private static final String PREFIX = "META-INF/services/";
而且还有值了。
然后去mysql的jar包下面/META-INF/services/java.sql.Driver文件里面内容:
com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver
看到这,后面就不看代码了。
总结一下:
所谓的SPI机制,就是说把接口和实现分离(通常不在一个jar包里)
也就是写接口的人和写实现类的人不是同一个。
然后接口的提供方会获取对应的实现,调用对应的方法。而具体的实现还是在实现类里面
一方提供规范(接口)和逻辑(具体调用哪些方法),另一方根据规范实现相应接口和方法。调用提供方给的静态方法即可