Java的SPI-ServiceLoader详解

Java的SPI-ServiceLoader详解

Java SPI机制

SPI的全名为Service Provider Interface.大多数开发人员可能不熟悉,因为这个是针对厂商或者插件的
我们系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块的方案,xml解析模块、jdbc模块的方案等。
面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,
如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。

Java SPI就是提供这样的一个机制:为某个接口寻×××实现的机制。

Java SPI的约定

当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。
该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入

基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。
jdk提供服务实现查找的一个工具类:java.util.ServiceLoader

前面说过SPI是针对厂商或者第三方插件的,但是那样不便于举例,不能很好的引导读者,我们先来个单机的。

服务(接口):一个IProgrammer接口
服务提供者(接口实现):两个实现类 GJProgrammer和JGSProgrammer
资源目录:META-INF/services

代码如下:

public interface IProgrammer {
    String title();
}
public class GJProgrammer implements IProgrammer {
    @Override
    public String title() {
        return "高级程序员";
    }
}
public class JGSProgrammer implements IProgrammer {
    static {
        System.out.println("加载了");
    }
    @Override
    public String title() {
        return "架构师";
    }
}

资源文件:
整体代码和资源如图所示:
Java的SPI-ServiceLoader详解_第1张图片
就是META-INF/services里边有一个名为IProgrammer限定名的文件,内容就是两个实现了类的限定名。

com.alipay.campus.demos.spi.GJProgrammer
com.alipay.campus.demos.spi.JGSProgrammer

测试类:

public static void main(String[] args) {
        ServiceLoader serviceLoader = ServiceLoader.load(IProgrammer.class);
        Iterator it = serviceLoader.iterator();
        while(it.hasNext()){
            System.out.println("Iterator next()方法调用..");
            IProgrammer iProgrammer = it.next();
            String title = iProgrammer.title();
            System.out.println("programmer title="+title);
        }
    }

输出结果:
Iterator< IProgrammer> next()方法调用..
programmer title=高级程序员
Iterator< IProgrammer> next()方法调用..
加载了
programmer title=架构师

分析

我们只是将实现类放到了META-INF/services下,文件名命名为IProgrammer接口限定名,内容放上两个实现类的限定名,然后使用ServiceLoader.loader 就能获取到所有实现类的实例,并能调用相应的接口方法。

此时我们自己是开发者也是使用者,我们完全可以自己new出这两个实现类,然后调用相应的方法。

换角度分析

我们可以换一个角度,以JDK获取数据库驱动为例:
JDK定义了一套接口规范(Driver接口)获取数据库驱动(jdk的jar),
mysql是数据库厂商(mysql-connector的jar),
oracle也是数据库厂商(oracle-connector的jar)

说道这里你大概知道是怎么回事了吧,jdk可不知道有哪些数据库厂商会接入,实现根本不在jdk哪里,在厂商哪里,那么程序员在写程序的时候引入jdk和mysql驱动怎么就能获取到mysql数据库的驱动了呢?

JDK和Mysql使用SPI原理

我们先来看下jdk是如何使用
DriverManager.getConnection(url,username,password)
DriverManager有一个静态代码块:

static {
    loadInitialDrivers();
    println("JDBC DriverManager initialized");
}

private static void loadInitialDrivers() {
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction() {
                public String run() {
                    return System.getProperty("jdbc.drivers");
                }
            });
        } catch (Exception ex) {
            drivers = null;
        }
        AccessController.doPrivileged(new PrivilegedAction() {
            public Void run() {
                //ServiceLoader的使用
                ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator driversIterator = loadedDrivers.iterator();
                try{
                    while(driversIterator.hasNext()) {
                        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驱动
Java的SPI-ServiceLoader详解_第2张图片

这次就明了!!!!

但是为什么要讲这个呢,主要是看SpringBoot的时候看了启动过程,有段代码很类似,在Spring中也有一种类似与Java SPI的加载机制。它在META-INF/spring.factories文件中配置接口的实现类名称,然后在程序中读取这些配置文件并实例化。
这种自定义的SPI机制是Spring Boot Starter实现的基础,引入一个jar包,里边有某个接口的实现并将接口实现放入到/META-INF/spring.factories里,SpringBoot应用就自动回装载这些类,就能完成一些拓展功能,是不是很强大。

你可能感兴趣的:(JDK底层,SpringBoot源码)