JDK SPI

图片

前言

之前看了spring带的SpringFactoriesLoader,他是spring框架下的一个工具类,是用来发现META-INF目录下的spring.factories文件的。 JDK自带了一个和SpringFactoriesLoader功能类似的一个工具类,叫ServiceLoader,不过这个类是发现项目中META-INF/services/目录下的文件的。 JDK自带的这种机制叫做SPI(Service Provider Interface),是Java提供给外部扩展或三方实现的一个机制。

正文

JDK SPI使用

使用方式也比较简单,ServiceLoader的注释上也都有说明:

  1. 服务方定义好一个接口或抽象类
  2. 三方或扩展方进行实现
  3. 三方或扩展方在jar的META-INF/services/目录下,创建一个 ‘接口类的全限定名’ 的文件
  4. 文件中的类容就是接口的实现类的全限定名,一行只能有一个,多个就多行。

下面进行自定义的步骤,这里为了方便,接口和实现就全在一个包里面的:

  1. 定义接口,所在包a.b.c.d 代码也比较简单,就是一个接口,其中定义一个方法。
1
2
3
4
5

public interface TestService {
    void test();
}

  1. 实现,所在包:a.b.c.d.impl,实现接口即可,自定义内容
1
2
3
4
5
6
7

public class TestServiceImpl implements TestService {
    public void test() {
        System.out.println("TestServiceImpl service run");
    }
}

  1. 在META-INF/services/目录下创建a.b.c.d.TestService 文件,然后内容就是a.b.c.d.impl.TestServiceImpl

  2. 调用

1
2
3
4
5
6
7
8
9
10
11
12

public class ServiceLoaderTest {
    public static void main(String[] args) {
        ServiceLoader<TestService> load = ServiceLoader.load(TestService.class);
        Iterator<TestService> iterator = load.iterator();
        while (iterator.hasNext()) {
            TestService next = iterator.next();
            next.test();
        }
    }
}

一次性加载了定义的文件中的所有的实现类,需要用迭代器进行迭代。

这样一个使用JDK的SPI机制的demo就完成了。

JDK SPI案例

第一个案例就是JDBC的DRIVER了,打开Driver,这个类是定义在JDK的java.sql下的。 然后引入MySQL的包:

1
2
3
4
5
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.25</version>
        </dependency>

会在该包下的META-INF/services/目录下发现一个java.sql.Driver 文件,内容是com.mysql.cj.jdbc.Driver。 然后打开这个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }

    public Driver() throws SQLException {
    }
}

然后打开DriverManager类,发现有个静态代码块,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }

    private static void loadInitialDrivers() {
        .... 省略 ...
        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;
            }
        });
        ... 省略...
        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);
            }
        }
    }

上面的代码主要就两个步骤,1. 使用ServiceLoader加载,然后调用 Class.forName()。

最后

看了JDK的SPI机制,收获还是挺大的。总结一下:

  1. ServiceLoader是根据接口加载文本中所有的实现类,若只需要其中一个实现类,则需要匹配需要的那个实现类
  2. 定义了一个标准,由扩展方或第三方去进行实现,服务方则不用去关注具体怎么实现的,实现解耦。
  3. ServiceLoader和SpringFactoriesLoader功能是类似的,只是一个是属于JDK的,一个是属于spring框架的
坚持原创技术分享,您的支持将鼓励我继续创作!