java中的spi机制
spi编程服务接口提供,我们首先看一个示例来理解java的spi编程
我们编写了一个接口UploadCDN,同时为他提供了两个实现类来提供提供服务,Test方法如下,同时在resources/META-INF/services下配置与与接口名称相同的文件名称的全路径,里面配置了两个实现类的路径
public class Test { public static void main(String[] args) { ServiceLoader<UploadCDN> serviceLoader=ServiceLoader.load(UploadCDN.class); for(UploadCDN u:serviceLoader){ u.upload("测试spi"); } } }
输出结果如下
重点就在serviceloader这个类,通过他的load方法,传入接口的类型。我们看一下serviceloader类,下图可知为什么要在META-INF/services目录下
serviceloader的load方法
public static <S> ServiceLoader<S> load(Class<S> service) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); }
调用了重载的方法,多传入了一个ClassLoader属性,后续用于加载我们配置的两个实现类,经过一系列的调用我们看到在reload方法中创建了一个LazyIterator,这个LazyIterator是一个内部类实现了Iterator,也就是一个内部的迭代器。
private class LazyIterator implements Iterator<S> { Class<S> service; ClassLoader loader; Enumeration<URL> configs = null; Iterator<String> pending = null; String nextName = null; private LazyIterator(Class<S> service, ClassLoader loader) { this.service = service; this.loader = loader; } private boolean hasNextService() { if (nextName != null) { return true; } if (configs == null) { try { String fullName = PREFIX + service.getName(); if (loader == null) configs = ClassLoader.getSystemResources(fullName); else configs = loader.getResources(fullName); } catch (IOException x) { fail(service, "Error locating configuration files", x); } } while ((pending == null) || !pending.hasNext()) { if (!configs.hasMoreElements()) { return false; } pending = parse(service, configs.nextElement()); } nextName = pending.next(); return true; } private S nextService() { if (!hasNextService()) throw new NoSuchElementException(); String cn = nextName; nextName = null; Class<?> c = null; try { c = Class.forName(cn, false, loader); } catch (ClassNotFoundException x) { fail(service, "Provider " + cn + " not found"); } if (!service.isAssignableFrom(c)) { fail(service, "Provider " + cn + " not a subtype"); } try { S p = service.cast(c.newInstance()); providers.put(cn, p); return p; } catch (Throwable x) { fail(service, "Provider " + cn + " could not be instantiated", x); } throw new Error(); // This cannot happen }
可以看到在这里记载了我们配置在配置文件中的两个实现类并进行了初始化,所以我们在Test方法中能够进行调用。
那么spi编程有什么作用呢,我们在学习数据库的时候,会记得市面上会有各种各样的数据库,他们的语法不尽相同,因此java提供了一个接口,由数据库厂商自己定义实现,我们需要那个数据库就导入相应的jar包即可,回忆一下jdbc连接数据库的代码
public static void main(String[] args) throws ClassNotFoundException, SQLException { //1.加载驱动程序 Class.forName("com.mysql.jdbc.Driver"); //2.获得数据库链接 Connection conn=DriverManager.getConnection(URL, USER, PASSWORD); //3.通过数据库的连接操作数据库,实现增删改查(使用Statement类) Statement st=conn.createStatement(); ResultSet rs=st.executeQuery("select * from user"); //4.处理数据库的返回结果(使用ResultSet类) while(rs.next()){ System.out.println(rs.getString("user_name")+" " +rs.getString("user_password")); } //关闭资源 rs.close(); st.close(); conn.close(); }
我们可以看一下一个数据库的jar
是不是和我们的测试很像呢,其实现在第一步classforname是不需要我们主动去做的原因就是spi编程,我们可以看看DriverManager类其中有一个静态代码块
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
可以看一下loadInitiaDrivers方法
是不是有些熟悉呢,在这个方法中使用了serviceloader去加载了META-INF下配置的com.mysql.jdbc.Driver类。