java · 2020-03-06 0

Java的SPI机制及AutoService

一、SPI机制

SPI:Service Provider Interface,是一种服务发现机制

它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类,模块之间基于接口编程,实现可拔插

二、栗子

1.定义接口

package com.zz.spi;

public interface SPIService {
    void execute();
}

2.实现类

SPIImpl1.java

package com.zz.spi;

public class SPIImpl1 implements SPIService {
    @Override
    public void execute() {
        System.out.println("SPIImpl1 execute()");
    }
}

SPIImpl2.java

package com.zz.spi;

public class SPIImpl2 implements SPIService {
    @Override
    public void execute() {
        System.out.println("SPIImpl2 execute()");
    }
}

3.定义文件

在ClassPath下,META-INF/services目录下创建一个以"接口全限定名"为命名的文件:

内容为实现类的全限定名:

com.zz.spi.SPIImpl1
com.zz.spi.SPIImpl2

4.使用

有两个类可以得到实现类的实例,sun.misc.Service和java.util.ServiceLoader

package com.zz.spi;

import sun.misc.Service;

import java.util.Iterator;
import java.util.ServiceLoader;

public class Main {
    public static void main(String[] args) {
        Iterator<SPIService> providers = Service.providers(SPIService.class);
        ServiceLoader<SPIService> load = ServiceLoader.load(SPIService.class);

        while (providers.hasNext()) {
            SPIService service = providers.next();
            service.execute();
        }
        System.out.println("- - - - - - - - - - - - - -");
        Iterator<SPIService> iterator = load.iterator();
        while (iterator.hasNext()) {
            SPIService service = iterator.next();
            service.execute();
        }
    }
}

结果:

三、JDBC中的应用

1.未用SPI机制

如果未用SPI机制,需要使用Class.forName()加载驱动

@Test
public void test1() throws SQLException, ClassNotFoundException {
    Class.forName("com.mysql.cj.jdbc.Driver");
    String url = "jdbc:mysql://localhost:3306";
    String user = "root";
    String password = "123456";
    Connection conn = DriverManager.getConnection(url, user, password);
    System.out.println(conn);
}

为什么需要加载驱动,查看源码,可以看出,当加载驱动时,会执行驱动类的static代码块,此代码块的作用是向DriverManager注册驱动,这样DriverManager.getConnection()便可以获得连接

2.使用SPI机制

对于高版本的jdk和mysql驱动,不再需要使用Class.forName()加载驱动

@Test
public void test2() throws SQLException {
    String url = "jdbc:mysql://localhost:3306";
    String user = "root";
    String password = "123456";
    Connection conn = DriverManager.getConnection(url, user, password);
    System.out.println(conn);
}

分析:

对于com.mysql.cj.jdbc.Driver,其jar文件META-INF/services目录下创建了java.sql.Driver,内容是Driver实现类的全限定名

对于DriverManager类

driversIterator.next()会创建Driver实例,所以此时会执行com.mysql.cj.jdbc.Driver的代码,注册到DriverManager

四、其他

1.AutoService

可以使用google的AutoService组件会自动在编译后的ClassPath下生成文件META-INF/services/com.zz.spi.SPIService文件

添加maven依赖

<dependency>
    <groupId>com.google.auto.service</groupId>
    <artifactId>auto-service</artifactId>
    <version>1.0-rc2</version>
</dependency>

在实现类上加上类注解@AutoService

SPIImpl1.java

package com.zz.spi;

import com.google.auto.service.AutoService;

@AutoService(SPIService.class)
public class SPIImpl1 implements SPIService {

    @Override
    public void execute() {
        System.out.println("SPIImpl1 execute()");
    }
}

SPIImpl2.java

package com.zz.spi;

import com.google.auto.service.AutoService;

@AutoService(SPIService.class)
public class SPIImpl2 implements SPIService {

    @Override
    public void execute() {
        System.out.println("SPIImpl2 execute()");
    }
}