01-反射
在之前的文章 反射机制 中,我们学习了 Java 中的反射机制。反射机制主要是为应用程序提供了在运行时访问类及其组成(Class 对象及其中的 Method、Constructor、Field 等对象)的接口。
反射在各类框架中都有广泛的应用,例如 Spring。
除了各类框架,JDK 中的许多机制也都是通过反射实现的,例如 SPI 机制。
02-SPI
SPI(Service Provider Interface)是 Java 6 引入的一种发现和加载特定接口实现的特性。它由四部分组成:
Service,一组接口或类,提供特定的功能或特性;
SPI,一个接口或抽象类,作为 1.中服务的代理或接入点;
1 和 2 组成了 Java 生态系统中常被提到的 API。
Service Provider,2.中 SPI 的特定实现;一般配置在 /META-INF/services/ 文件夹下以 SPI 全量名为文件名,文件内容为服务提供者,即具体的特定实现。
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.jar 和 hsqldb-2.3.4.jar 作为 Servcie Provider 提供了对java.sql.Driver的实现。
注:我们检查上述两个 jar 包可以发现,在它们的 META-INF/serivces 下存在一个名为 java.sql.Driver 的文件,其内容分别是com.mysql.cj.jdbc.Driver和org.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.SLF4JLogFactory和org.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- 线程基础
评论