Java Core 「6」反射与SPI机制

288 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第2天,点击查看活动详情

01-反射

在之前的文章 反射机制 中,我们学习了 Java 中的反射机制。反射机制主要是为应用程序提供了在运行时访问类及其组成(Class 对象及其中的 Method、Constructor、Field 等对象)的接口。

反射在各类框架中都有广泛的应用,例如 Spring。

除了各类框架,JDK 中的许多机制也都是通过反射实现的,例如 SPI 机制。

02-SPI

SPI(Service Provider Interface)是 Java 6 引入的一种发现和加载特定接口实现的特性。它由四部分组成:

  1. Service,一组接口或类,提供特定的功能或特性;

  2. SPI,一个接口或抽象类,作为1.中服务的代理或接入点;

    1和2组成了Java生态系统中常被提到的API。

  3. Service Provider,2.中 SPI 的特定实现;一般配置在 /META-INF/services/ 文件夹下以 SPI 全量名为文件名,文件内容为服务提供者,即具体的特定实现。

  4. ServiceLoader,java.util.ServiceLoader,SPI 机制的核心,用来发现并加载 Service Provider;

上述组件之间的关系如下图所示:

图 1. SPI 机制中各组件之间的关系

图 1. SPI 机制中各组件之间的关系

02.1-SPI in JDBC

JDBC 中加载不同数据库的驱动是最常见的 SPI 应用。Java 中定义了 SPI java.sql.Driver,在java.sql.DriverManager中使用java.util.ServiceLoader对不同实现进行发现并加载。mysql-connector-java-8.0.7-dmr.jarhsqldb-2.3.4.jar 作为 Servcie Provider 提供了对java.sql.Driver的实现。

注:我们检查上述两个 jar 包可以发现,在它们的 META-INF/serivces 下存在一个名为 java.sql.Driver 的文件,其内容分别是com.mysql.cj.jdbc.Driverorg.hsqldb.jdbc.JDBCDriver

java.sql.DriverManager中加载 SPI 实现的代码如下:

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

try {
	while (driversIterator.hasNext()) {
		driversIterator.next();
	}
} catch (Throwable t) {
	// Do nothing
}

02.2-SPI in JCL

JCL(Jakarta Commons Logging)是 Jakarta EE 中日志库门面实现。org.apache.commons.logging.LogFactory#getFactory 方法中使用了 SPI 来加载具体的日志库实现。

// Determine which concrete LogFactory subclass to use.
// 1. First, try a global system property
try {
    // FACTORY_PROPERTY = "org.apache.commons.logging.LogFactory"
		// 全局变量
    String factoryClass = getSystemProperty(FACTORY_PROPERTY, null);
}...
// 2. Second, try to find a service by using the JDK1.3 class
// discovery mechanism, which involves putting a file with the name
// of an interface class in the META-INF/services directory, where the
// contents of the file is a single line specifying a concrete class
// that implements the desired interface.
try {
    // SERVICE_ID = "META-INF/services/org.apache.commons.logging.LogFactory"
    // SPI 机制
    final InputStream is = getResourceAsStream(contextClassLoader, SERVICE_ID);
}...
// 3. Third try looking into the properties file read earlier (if found)
try {
    // FACTORY_PROPERTY = "org.apache.commons.logging.LogFactory"
    // classpath根目录commons-logging.properties中
    // org.apache.commons.logging.LogFactory属性
    String factoryClass = props.getProperty(FACTORY_PROPERTY);
}...
// 4. Fourth, try the fallback implementation class
// FACTORY_DEFAULT = "org.apache.commons.logging.impl.LogFactoryImpl"
// 默认实现
factory = newFactory(FACTORY_DEFAULT, thisClassLoader, contextClassLoader);

jcl-over-slf4j-1.7.32.jar 和 spring-jcl-5.1.5.RELEASE.jar 都属于org.apache.commons.logging.LogFactory的 Service Provider,它们在 META-INF/services 中分别制定了具体的实现:org.apache.commons.logging.impl.SLF4JLogFactoryorg.apache.commons.logging.LogFactoryService

02.3-SPI in Spring

Spring 中参考 SPI 机制实现了类似的服务发现机制。org.springframework.core.io.support.SpringFactoriesLoader类实现了对 META-INF/spring.factories 文件发现与文件内类的加载。

org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories

// 加载 META-INF/spring-factories 文件
Enumeration<URL> urls = classLoader != null 
? classLoader.getResources("META-INF/spring.factories") 
: ClassLoader.getSystemResources("META-INF/spring.factories");

02.4-SPI in Eclipse Plugin

此处内容参考 pdai.tech : SPI机制 - 插件体系

03-SPI 开发

定义一个 MySpi 抽象类,其包含一个抽象方法 hi

package self.samson.example.basic.spi;

public abstract class MySpi {
    public abstract void hi() ;
}

实现一个用中文说你好的 Service Provider:

package self.samson.example.basic.spi;

public class ChineseSpi extends MySpi{
    @Override
    public void hi() {
        System.out.println("你好!");
    }
}
// 将 META-INFO/services/self.samson.example.basic.spi.MySpi 打包进 zh.jar
// 内容为:self.samson.example.basic.spi.ChineseSpi

实现一个用英文说 hi 的 Service Provider:

package self.samson.example.basic.spi;

public class EnglishSpi extends MySpi{
    @Override
    public void hi() {
        System.out.println("hello!");
    }
}
// 将 META-INFO/services/self.samson.example.basic.spi.MySpi 打包进 en.jar
// 内容为:self.samson.example.basic.spi.EnglishSpi 

编写测试类,运行时需要将 zh.jar en.jar 导入到 classpath:

package self.samson.example.basic.spi;

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

public class SpiExamples {

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

输出结果为:
你好!
hello!


历史文章