写点什么

Java Core「6」反射与 SPI 机制

作者:Samson
  • 2022 年 5 月 26 日
  • 本文字数:2662 字

    阅读完需:约 9 分钟

Java Core「6」反射与SPI机制

01-反射

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


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


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

02-SPI

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


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

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

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

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

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


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



图 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 propertytry {    // 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!


历史文章

Java Core「5」自定义注解编程

Java Core「4」java.util.concurrent 包简介

Java Core「3」volatile 关键字

Java Core「2」synchronized 关键字

Java Core「1」JUC- 线程基础

发布于: 刚刚阅读数: 4
用户头像

Samson

关注

还未添加个人签名 2019.07.22 加入

还未添加个人简介

评论

发布
暂无评论
Java Core「6」反射与SPI机制_学习笔记_Samson_InfoQ写作社区