- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
基于【JDK1.8】 。
SPI即 service-provider-interface 的简写; 。
JDK内置的服务提供加载机制,可以为服务接口加载实现类,解耦是其核心思想,也是很多框架和组件的常用手段; 。
就是普通的接口,在SPI的机制中称为【service】,即服务; 。
public interface Animal {
String animalName () ;
}
提供两个模拟用来测试,就是普通的接口实现类,在SPI的机制中称为【service-provider】即服务提供方; 。
CatAnimal 实现类; 。
public class CatAnimal implements Animal {
@Override
public String animalName() {
System.out.println("Cat-Animal:布偶猫");
return "Ragdoll";
}
}
DogAnimal 实现类; 。
public class DogAnimal implements Animal {
@Override
public String animalName() {
System.out.println("Dog-Animal:哈士奇");
return "husky";
}
}
文件目录:在代码工程中创建 META-INF.services 文件夹; 。
文件命名: butte.program.basics.spi.inf.Animal ,即全限定接口名称; 。
文件内容:添加相应实现类的全限定命名; 。
butte.program.basics.spi.impl.CatAnimal
butte.program.basics.spi.impl.DogAnimal
通过 ServiceLoader 加载配置文件中指定的服务实现类,然后遍历并调用 Animal 接口方法,从而执行不同服务提供方的具体逻辑; 。
public class SpiAnaly {
public static void main(String[] args) {
ServiceLoader<Animal> serviceLoader = ServiceLoader.load(Animal.class);
Iterator<Animal> animalIterator = serviceLoader.iterator();
while(animalIterator.hasNext()) {
Animal animal = animalIterator.next();
System.out.println("animal-name:" + animal.animalName());
}
}
}
结果输出 。
Cat-Animal:布偶猫 \n animal-name:ragdoll
Dog-Animal:哈士奇 \n animal-name:husky
很显然,分析SPI机制的原理,从 ServiceLoader 源码中 load 方法切入即可,但是需要先从核心类的结构开始分析; 。
public final class ServiceLoader<S> implements Iterable<S> {
// 配置文件目录
private static final String PREFIX = "META-INF/services/";
// 表示正在加载的服务的类或接口
private final Class<S> service;
// 类加载器用来定位,加载,实例化服务提供方
private final ClassLoader loader;
// 创建ServiceLoader时采用的访问控制上下文
private final AccessControlContext acc;
// 按实例化的顺序缓存服务提供方
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// 惰性查找迭代器
private LazyIterator lookupIterator;
/**
* service:表示服务的接口或抽象类
* loader: 类加载器
*/
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
/**
* ServiceLoader构造方法
*/
private ServiceLoader(Class<S> svc, ClassLoader cl) {
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
public void reload() {
providers.clear();
// 实例化迭代器
lookupIterator = new LazyIterator(service, loader);
}
public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader) {
return new ServiceLoader<>(service, loader);
}
private class LazyIterator implements Iterator<S> {
// 服务接口
Class<S> service;
// 类加载器
ClassLoader loader;
// 实现类URL
Enumeration<URL> configs = null;
// 实现类全名
Iterator<String> pending = null;
// 下个实现类全名
String nextName = null;
}
}
断点截图:
在 ServiceLoader 类的迭代器方法中,实际使用的是 LazyIterator 内部类的方法; 。
public Iterator<S> iterator() {
return new Iterator<S>() {
Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
从上面迭代方法的源码中可知,最终执行的是 LazyIterator#hasNextService 判断方法,该方法通过解析最终会得到实现类的全限定名称; 。
private class LazyIterator implements Iterator<S> {
private boolean hasNextService() {
// 1、拼接名称
String fullName = PREFIX + service.getName();
// 2、加载资源文件
configs = loader.getResources(fullName);
// 3、解析文件内容
pending = parse(service, configs.nextElement());
nextName = pending.next();
return true;
}
}
断点截图:
迭代器的 next 方法最终执行的是 LazyIterator#nextService 获取方法,会基于上面 hasNextService 方法获取的实现类全限定名称,获取其 Class 对象,进而得到实例化对象,缓存并返回; 。
private class LazyIterator implements Iterator<S> {
private S nextService() {
// 1、通过全限定命名获取Class对象
String cn = nextName;
Class<?> c = Class.forName(cn, false, loader);
// 2、实例化对象
S p = service.cast(c.newInstance());
// 3、放入缓存并返回该对象
providers.put(cn, p);
return p;
}
}
断点截图:
在JDK中提供了数据库驱动接口 java.sql.Driver ,无论是MySQL驱动包还是Druid连接池,都提供了该接口的实现类,通过SPI机制可以加载到这些驱动实现类; 。
public class DriverManager {
private static void loadInitialDrivers() {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(java.sql.Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
}
});
}
}
断点截图:
SLF4J是门面模式的日志组件,提供了标准的日志服务 SLF4JServiceProvider 接口,在 LogFactory 日志工厂类中,负责加载具体的日志实现类,比如常用的Log4j或Logback日志组件; 。
public final class LoggerFactory {
static List<SLF4JServiceProvider> findServiceProviders() {
// 服务加载
ClassLoader classLoaderOfLoggerFactory = org.slf4j.LoggerFactory.class.getClassLoader();
// 重点看该方法:【getServiceLoader()】
ServiceLoader<SLF4JServiceProvider> serviceLoader = getServiceLoader(classLoaderOfLoggerFactory);
// 迭代方法
List<SLF4JServiceProvider> providerList = new ArrayList();
Iterator<SLF4JServiceProvider> iterator = serviceLoader.iterator();
while(iterator.hasNext()) {
safelyInstantiate(providerList, iterator);
}
return providerList;
}
}
断点截图:
文档仓库:
https://gitee.com/cicadasmile/butte-java-note
应用仓库:
https://gitee.com/cicadasmile/butte-flyer-parent
最后此篇关于JDK中「SPI」原理分析的文章就讲到这里了,如果你想了解更多关于JDK中「SPI」原理分析的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我的内部类如下: public class ClassWithInnerObject { private final InnerObject innerObject; public Class
我在 STM32F4 Discovery 上安装了 STM32F4407VGT6 Controller 。我尝试使用 SPI + DMA 从 AD7683 ADC 读取数据,但 DMA 接收缓冲区始终
假设您有一个只有一个片选的 SPI 总线。 是否有可以将 8 个或更多设备连接到该 SPI 总线的芯片? 为简化起见,您可以假设所有设备都同意 SPI 模式(数据需要在上升沿有效)。此外,所有设备都是
我有一个 32 GB 的金士顿 SDHC microSD 卡,它必须与 MSP430F2618 通信通过 SPI .我无法通过使用 CMD55 + ACMD41(bit30 设置为 1)来初始化它,如
我正在使用 chai-spies 来确保调用 Controller 中的函数,这是我的测试: it('Should show right season and analysts when compet
我刚开始进行 JS 单元测试,但很难理解如何使用 Jasmine spy 创建有意义的测试。 it('should take an array of shopping items', function
我一直在阅读 SPI 以及如何创建内核驱动程序,但我仍然不确定所有这些是如何工作的。 例如: static struct spi_driver ds1305_driver = {
这是我正在测试的代码 eventsApp.factory('userData', ['userResource', function(userResource){ return{ ge
我有一个简单的组件: .html: {{title}} Change title .ts: export class AppComponent { title = 'app works!'
当我从 SPI 总线上的 PIC-18F4520 向我的卡发出地址为 (0x00000000) 的 cmd17 时,我从命令问题中获得了正确的返回 R1 token 。然后,经过几次循环检查后,我从发
我正在使用rspec-spies我想知道在打完所有电话后是否有办法检查 spy 。 例如,如果我做类似的事情 # setup Post.stub(:find).with(@post.id).and_r
我正在使用 rspec-spies并且想知道是否有办法在所有电话都打完后检查 spy 。 例如,如果我做类似的事情 # setup Post.stub(:find).with(@post.id).an
我正在尝试制作一个可以点击另一个程序按钮的程序。我被告知我需要使用 spy++ 来获取我想要点击的按钮的 ID,所以我现在正在使用它。我找到了包含我希望从中获取按钮 ID 的按钮的窗口(窗口中有 3
问题 在我们的代码库中,我们有一个 sinon 问题,可以使用下面的代码片段重现。问题是,它似乎是间接调用的 spy 返回力 false,console.log 明确指出该方法被调用但 spy.cal
考虑以下 react 组件。 import React, { Component } from "react"; import { reduxForm, Field } from "redux-for
我正在开发一个 Linux spi 驱动程序来处理通过 SPI 端口的通信。 我的 SoC 提供了三个 spi 模块(我将其理解为端口),称为 ecspi1/ecspi2/ecspi3。 我需要使用
当我将类的方法包装成这样的 Sinon-spy 时: sinon.spy(myObject, "myMethod") spy 内部会发生什么? 我猜 spy 对象有一个指向“myObject.myMe
我正在为遗留代码编写一些 JUnit 测试,并且我非常喜欢使用注释。我想知道是否可以创建一个 spy 对象的声明,然后实例化它。我问的原因是因为我有一个带有非空构造函数的类。该构造函数的值直到测试用例
我有两个不同的设备要连接 Arduino .一个Ethernet屏蔽和轴编码器。第一个有 SPI模式 0 和第二个 SPI 模式 2。它们冲突。这个问题有解决方案吗? 我使用不同的芯片选择引脚,这两个
我正在运行使用 Yocto (Pyro) 构建的嵌入式 Linux (4.14.16)。我在具有 i.MX6DL 且 SPI 连接到 FPGA(Xilinx Artix 7)的定制板上运行。我目前正在
我是一名优秀的程序员,十分优秀!