java spi机制

>>强大,10k+点赞的 SpringBoot 后台管理系统竟然出了详细教程!

从JDBC说起

java JDBC的代码用起来其实是比较麻烦的,例如下面的例子

String DBDRIVER = "com.mysql.jdbc.Driver";  
String DBURL = "jdbc:mysql://localhost:3303/javadata";  
String DBUSER = "root";  
String DBPASS = "mysqladmin";   
Connection conn = null;   
try{    
    Class.forName("com.mysql.jdbc.Driver");   
}catch(ClassNotFoundException e){    
    e.printStackTrace();   
}     
String sql = "";   
try {    
    conn = DriverManager.getConnection(DBURL, DBUSER, DBPASS);
    System.out.println(conn);    
    Statement stmt = conn.createStatement();   
    stmt.executeUpdate(sql);    
catch (SQLException e1) {    
    e1.printStackTrace();   
}

首先需要 Class.forName去加载实现类。在实现类里,使用静态代码块,往DriverManager中注册driver。这个是为了后面可以使用DriverManager.getConnection来创建连接。 具体创建连接的是driver对象。DriverManager帮忙找到正确的driver然后获取连接。

  static
  {
    try
    {
      DriverManager.registerDriver(new Driver());
    }
    catch (SQLException E)
    {
      throw new RuntimeException("Can't register driver!");
    }
  }

那其实完全不需要DriverManager,如果自己可以找到对应的driver就可以获取连接了。

仔细思考

很多人学习jdbc的时候就这么写代码,都习惯了,没有感觉不好。但是从设计的角度来看,jdbc是java的规范,你如果想使用,你还得知道com.mysql.jdbc.Driver这个具体的实现类。那这样岂不是很麻烦,除了学习规范以外,还得了解数据库的实现情况。现在国产数据库那么多,这样操作下来岂不是要记住很多类。这就是spi的初衷。彻底在程序员的角度屏蔽掉实现。

spi的挑战

spi的目的很明确了。还是jdbc的例子,就是想纯粹依靠java提供的规范来达到操作的目的。我们只需要知道DriverManager即可。代码只需要下面的一行即可。

 conn = DriverManager.getConnection(DBURL, DBUSER, DBPASS);

那么明显的问题就来了,接口类如何与实现类关联的。

java用了最简单的方式来保证关联。META-INF/services里写了接口类和实现类的名字。例如mysql的驱动包里有一个java.sql.Driver的文件,内容是com.mysql.jdbc.Driver。

有了这个机制就需要一个管理者的角色来读取这个文件并且完成类加载。ServiceLoader登上舞台。这样看起来是不是把问题都解决了。让ServiceLoader读取文件内容,然后顺利完成类加载,看着一切很合理。但是ServiceLoader也是java的提供的类。他是boostrap classloader加载的。他如何去加载application classloader加载的类呢?如果没有get到这个问题的,需要了解一下classloader加载类的规则。

为了解决类加载的问题,又得加入一个机制保证类加载。于是线程上下文classloader也登场了。每个线程可以存放classloader的对象,默认是当前运行的classloader。可以从线程里拿出classloader来继续做类加载。

为了解决这个问题,spi加入两个成员:ServiceLoader和ContextClassLoader。

jdk实现

上面的思路大概了解了。那我们最后过一下jdk的代码来回顾一下。
DriverManager中有如下代码保证加载Driver

ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
 Iterator<Driver> driversIterator = loadedDrivers.iterator();

ServiceLoader

private static final String PREFIX = "META-INF/services/";

成员变量里写死了前缀。

    public static <S> ServiceLoader<S> load(Class<Sservice{
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

构造方法中传入了线程上下文classloader。

    c = Class.forName(cn, false, loader);

最后用Class.forName加载了实现类。

spi的扩展

jdk通过这个手段,彻底屏蔽掉了实现类,纯粹依靠配置文件做到互相发现。我们也可以模拟jdbc的方式,做到屏蔽底层实现。


原文始发于微信公众号(一次编译多处运行):java spi机制