Java学习之SPI、JDBC、SpringFactoriesLoader、Dubbo

news2024/11/16 9:19:41

概述

SPI,Service Provider Interface,一种服务发现机制,指一些提供给你继承、扩展,完成自定义功能的类、接口或方法。

在SPI机制中,服务提供者为某个接口实现具体的类,而在运行时通过SPI机制,查找到对应的实现类,并将其加载进行使用。

JDK 6(参考java.util.ServiceLoader类的注释Since字段)引进的一个特性,可实现动态加载具体实现类的机制,通过它可以具体实现代码的解耦,也可实现类似于IoC的效果。

SPI的实现方式:提供实现的实现类打包成jar文件,jar文件里面必须有个META-INF/services目录,其下有一个文本文件,文件名即为SPI接口的全限定名(完整路径名),文件的内容是该jar包中提供的SPI接口的实现类名。文件编码是UTF-8。

作用有两个:

  • 把标准定义和接口实现分离,在模块化开发中很好地实现解耦
  • 实现功能的扩展,更好地满足定制化的需求

SPI的不足之处:不能根据需求去加载扩展实现,每次都会加载扩展接口的所有实现类并进行实例化,实例化会造成性能开销,并且加载一些不需要用到的实现类,会导致内存资源的浪费。

API和SPI

API,提供给他人使用的具备某项功能的类、接口或方法。

SPI用于一些通用的标准中,为标准的实现产商提供扩展点。标准在上层提供API,API内部使用SPI,当API被客户使用时,会动态得从当前运行的classpath中寻找该SPI的实现,然后使用该SPI的实现来完成API的功能。

实例

JDBC

java.sql.Driver数据库驱动接口,JDK中只提供接口的定义,具体的实现类由各个数据库厂商提供的驱动包来完成,程序在运行的时候会根据当前导入的驱动包来完成对应数据库的连接。
在这里插入图片描述
如上图,java.sql.Driver文件内容:com.mysql.cj.jdbc.Driver

同理也能在postgresql-42.7.2.jar下找到java.sql.Driver文件,内容为org.postgresql.Driver,截图省略。

com.mysql.cj.jdbc.Driver源码很简单:

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    // Register ourselves with the DriverManager.
    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }

    public Driver() throws SQLException {
    }
}

核心方法就一行:DriverManager.registerDriver(new Driver());,在PG源码org.postgresql.Driver里也能找到DriverManager.registerDriver()

基于JDK 22源码,在DriverManager类里搜索文件名java.sql.Driver,找到方法ensureDriversInitialized:

/*
 * Load the initial JDBC drivers by checking the System property jdbc.drivers and then use the ServiceLoader mechanism
 */
private static void ensureDriversInitialized() {
	// 检查驱动是否已经初始化成功
    if (driversInitialized) {
        return;
    }
    synchronized (lockForInitDrivers) {
    	// 优先从环境变量jdbc.drivers获取驱动
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                    return System.getProperty("jdbc.drivers");
                }
            });
        } catch (Exception ex) {
            drivers = null;
        }
        // If the driver is packaged as a Service Provider, load it.
        // Get all the drivers through the classloader exposed as a java.sql.Driver.class service.
        // ServiceLoader.load() replaces the sun.misc.Providers()
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
            	// ServiceLoader
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                /* 加载驱动然后初始化,存在一种情况,有java.sql.Driver文件,但是文件内容为空,
                 * 或找不到文件内容指向的Driver类,用Throwable来catch java.util.ServiceConfigurationError
                 */
                try {
                	// TODO: loadedDrivers并没有赋值到drivers?
                    while (driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch (Throwable t) {
                    // Do nothing
                }
                return null;
            }
        });
        if (drivers != null && !drivers.isEmpty()) {
            String[] driversList = drivers.split(":");
            for (String aDriver : driversList) {
                try {
                    Class.forName(aDriver, true, ClassLoader.getSystemClassLoader());
                } catch (Exception ex) {
                	// just logging
                }
            }
        }
        // 设置驱动初始化成功标志
        driversInitialized = true;
    }
}

ensureDriversInitialized()方法的调用方有getDriver()getConnection()两个,即获取驱动和获取连接。

Spring

在Spring源码里也有SPI的影子,如spring.factories文件和SpringFactoriesLoader,两者一起构成Spring Boot Starter实现的基础。

以3.2.4版本来讲解,位于spring-boot-actuator-autoconfigure-3.2.4.jar下的spring.factories文件片段:

# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.actuate.autoconfigure.metrics.ValidationFailureAnalyzer,\
org.springframework.boot.actuate.autoconfigure.health.NoSuchHealthContributorFailureAnalyzer

可见,此文件实际上是.properties文件格式,不同类型的配置项用空行隔开,方便阅读,支持#作为注释字符。配置是键值对形式,Key和Value都是代表类的完整包名,Value可以有多个,用,分隔,多行文本以\分隔;并且Key是接口、注解、或抽象类,Value是Key的实现类。

而SpringFactoriesLoader自spring 3.2版本开始就已经存在,其属性如下:

// spring.factories文件默认检索位置
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
// 默认抛出异常
private static final FailureHandler THROWING_FAILURE_HANDLER = FailureHandler.throwing();
// ClassLoader级别的缓存,Value也是Map,K为资源目录,V为全路径类
static final Map<ClassLoader, Map<String, SpringFactoriesLoader>> cache = new ConcurrentReferenceHashMap<>();
// 用于加载类
@Nullable
private final ClassLoader classLoader;
// 用于保存spring.factories文件里的键值对
private final Map<String, List<String>> factories;

核心方法有两个,forResourceLocation是一个静态方法,用于获取实例:

public static SpringFactoriesLoader forResourceLocation(String resourceLocation, @Nullable ClassLoader classLoader) {
	Assert.hasText(resourceLocation, "'resourceLocation' must not be empty");
	ClassLoader resourceClassLoader = (classLoader != null ? classLoader : SpringFactoriesLoader.class.getClassLoader());
	Map<String, SpringFactoriesLoader> loaders = cache.computeIfAbsent(resourceClassLoader, key -> new ConcurrentReferenceHashMap<>());
	return loaders.computeIfAbsent(resourceLocation, key -> new SpringFactoriesLoader(classLoader, loadFactoriesResource(resourceClassLoader, resourceLocation)));
}

参考resourceLocation可以传入指定的路径,如果为空,则从META-INF/spring.factories查找。

load方法则用于获取指定类型的实现类集合:

public <T> List<T> load(Class<T> factoryType, @Nullable ArgumentResolver argumentResolver,
		@Nullable FailureHandler failureHandler) {
	Assert.notNull(factoryType, "'factoryType' must not be null");
	List<String> implementationNames = loadFactoryNames(factoryType);
	List<T> result = new ArrayList<>(implementationNames.size());
	FailureHandler failureHandlerToUse = (failureHandler != null) ? failureHandler : THROWING_FAILURE_HANDLER;
	for (String implementationName : implementationNames) {
		T factory = instantiateFactory(implementationName, factoryType, argumentResolver, failureHandlerToUse);
		if (factory != null) {
			result.add(factory);
		}
	}
	AnnotationAwareOrderComparator.sort(result);
	return result;
}

Dubbo

打开dubbo-3.2.14源码,发现META-INF/services目录下有个org.apache.dubbo.common.extension.LoadingStrategy文本文件,内容如下:

org.apache.dubbo.common.extension.DubboInternalLoadingStrategy
org.apache.dubbo.common.extension.DubboLoadingStrategy
org.apache.dubbo.common.extension.ServicesLoadingStrategy

DubboInternalLoadingStrategy用于查找META-INF/dubbo/internal/目录下的文件,DubboLoadingStrategy用于查找META-INF/dubbo/目录下的文件,ServicesLoadingStrategy用于查找META-INF/services/目录下的文件。

META-INF/dubbo/internal/目录下有91个文件,所有文件名都是Dubbo下的全路径名指向的接口类,这些接口都添加有注解@SPI(value = "", scope = FRAMEWORK),文件内容则是多行键值对,采用Key=Value形式,Key是一个简称,Value则是Dubbo下接口(文件名对应的)的实现类。

SPI注解源码如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {
    String value() default "";
    ExtensionScope scope() default ExtensionScope.APPLICATION;
}

ExtensionScope是一个枚举类:

  • FRAMEWORK:
  • APPLICATION:默认值
  • MODULE:
  • SELF:

ExtensionLoader是个泛型类,入口方法是getExtension:

public T getExtension(String name, boolean wrap) {
    checkDestroyed();
    if (StringUtils.isEmpty(name)) {
        throw new IllegalArgumentException("Extension name == null");
    }
    if ("true".equals(name)) {
    	// 获取默认拓展实现类
        return getDefaultExtension();
    }
    String cacheKey = name;
    if (!wrap) {
        cacheKey += "_origin";
    }
    // Holder机制包装实现类+缓存
    final Holder<Object> holder = getOrCreateHolder(cacheKey);
    Object instance = holder.get();
    // DCL
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
            	// 核心方法:创建实现类
                instance = createExtension(name, wrap);
                holder.set(instance);
            }
        }
    }
    return (T) instance;
}

接下来看看createExtension方法:

private T createExtension(String name, boolean wrap) {
    Class<?> clazz = getExtensionClasses().get(name);
    if (clazz == null || unacceptableExceptions.contains(name)) {
        throw findException(name);
    }
    try {
        T instance = (T) extensionInstances.get(clazz);
        if (instance == null) {
            extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));
            instance = (T) extensionInstances.get(clazz);
            // 实例化前处理
            instance = postProcessBeforeInitialization(instance, name);
            // 向实例中注入依赖
            injectExtension(instance);
            // 实例化后处理
            instance = postProcessAfterInitialization(instance, name);
        }
        if (wrap) {
            List<Class<?>> wrapperClassesList = new ArrayList<>();
            if (cachedWrapperClasses != null) {
                wrapperClassesList.addAll(cachedWrapperClasses);
                wrapperClassesList.sort(WrapperComparator.COMPARATOR);
                Collections.reverse(wrapperClassesList);
            }
            if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
                for (Class<?> wrapperClass : wrapperClassesList) {
                    Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
                    boolean match = (wrapper == null) || ((ArrayUtils.isEmpty(wrapper.matches()) || ArrayUtils.contains(wrapper.matches(), name)) && !ArrayUtils.contains(wrapper.mismatches(), name));
                    if (match) {
                    	// 将当前instance作为参数传给Wrapper的构造方法,并通过反射创建Wrapper实例,向Wrapper实例中注入依赖,最后将Wrapper实例再次赋值给instance变量
                        instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                        instance = postProcessAfterInitialization(instance, name);
                    }
                }
            }
        }
        // Warning: After an instance of Lifecycle is wrapped by cachedWrapperClasses, it may not still be Lifecycle instance, this application may not invoke the lifecycle.initialize hook.
        initExtension(instance);
        return instance;
    } catch (Throwable t) {
    	// 创建实现类失败
        throw new IllegalStateException();
    }
}

核心方法是getExtensionClasses:

private Map<String, Class<?>> getExtensionClasses() {
	// 检查缓存
    Map<String, Class<?>> classes = cachedClasses.get();
    // DCL
    if (classes == null) {
        synchronized (cachedClasses) {
            classes = cachedClasses.get();
            if (classes == null) {
                try {
                    classes = loadExtensionClasses();
                } catch (InterruptedException e) {
                    logger.error();
                    throw new IllegalStateException();
                }
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}

继续看loadExtensionClasses:

private Map<String, Class<?>> loadExtensionClasses() throws InterruptedException {
    checkDestroyed();
    cacheDefaultExtensionName();
    Map<String, Class<?>> extensionClasses = new HashMap<>();
    for (LoadingStrategy strategy : strategies) {
        loadDirectory(extensionClasses, strategy, type.getName());
        // compatible with old ExtensionFactory
        if (this.type == ExtensionInjector.class) {
            loadDirectory(extensionClasses, strategy, ExtensionFactory.class.getName());
        }
    }
    return extensionClasses;
}

继续看loadDirectory,两个逻辑,一是调用方法loadDirectoryInternal,二是判断Dubbo 2的向下兼容性,核心方法是loadDirectoryInternal,继续调用方法loadResource,用于读取和解析配置文件,并通过反射加载类,这里面可看到对前面提到的文本文件进行逐行解析,判断=分隔符的逻辑,最后调用 loadClass方法用于操作缓存。loadClass方法调用缓存相关方法,如cachedAdaptiveClass、cachedWrapperClasses和cachedNames等。

参考

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1928427.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Facebook未来展望:数字社交平台的进化之路

在信息技术日新月异的时代&#xff0c;社交媒体平台不仅是人们交流沟通的重要工具&#xff0c;更是推动社会进步和变革的重要力量。作为全球最大的社交媒体平台之一&#xff0c;Facebook在过去十多年里&#xff0c;不断创新和发展&#xff0c;改变了数十亿用户的社交方式。展望…

构建企业多层社会传播网络:以AI智能名片S2B2C商城小程序为例

摘要&#xff1a;在数字化转型的浪潮中&#xff0c;企业如何有效构建并优化其社会传播网络&#xff0c;已成为提升市场竞争力、深化用户关系及实现价值转化的关键。本文以AI智能名片S2B2C商城小程序为例&#xff0c;深入探讨如何通过一系列精细化的策略与技术创新&#xff0c;构…

IP地址知识点

一、IP地址组成 把一个IP地址分成两部分&#xff1a;网络号&#xff08;标识了一个局域网&#xff09;主机号&#xff08;标识了一个局域网中的设备&#xff09; 下图是通过一个路由器连接的两个局域网&#xff08;两个相邻的局域网&#xff09;&#xff0c;网络号不相同&…

AI绘画入门实践|Midjourney 的模型版本

模型分类 Midjourney 的模型主要分为2大类&#xff1a; 默认模型&#xff1a;目前包括&#xff1a;V1, V2, V3, V4, V5.0, V5.1, V5.2, V6 NIJI模型&#xff1a;目前包括&#xff1a;NIJI V4, NIJI V5, NIJI V6 模型切换 你在服务器输入框中输入 /settings&#xff1a; 回车后…

Mac电脑清理软件有哪些 MacBooster和CleanMyMac哪个好用 苹果电脑清理垃圾软件推荐 cleanmymac和柠檬清理

对于苹果电脑用户来说&#xff0c;‌选择合适的清理软件可以帮助优化电脑性能&#xff0c;‌释放存储空间&#xff0c;‌并确保系统安全。一款好用的苹果电脑清理软件&#xff0c;能让Mac系统保持良好的运行状态&#xff0c;避免系统和应用程序卡顿的产生。有关Mac电脑清理软件…

什么是MOW,以bitget钱包为例

元描述&#xff1a;MOW凭借其富有创意的故事情节和广阔的潜力在Solana上脱颖而出。本文深入探讨了其独特的概念和光明的未来。 Mouse in a Cats World (MOW)是一个基于Solana区块链的创新meme项目&#xff0c;它重新构想了一个异想天开且赋予权力的故事。在这个奇幻的宇宙中&am…

JuiceFS、Ceph 和 MinIO 结合使用

1. 流程图 将 JuiceFS、Ceph 和 MinIO 结合使用&#xff0c;可以充分利用 Ceph 的分布式存储能力、JuiceFS 的高性能文件系统特性&#xff0c;以及 MinIO 提供的对象存储接口。以下是一个方案&#xff0c;介绍如何配置和部署 JuiceFS 使用 Ceph 作为其底层存储&#xff0c;并通…

非法闯入智能监测摄像机:安全守护的新利器

在当今社会&#xff0c;安全问题愈发受到重视。随着科技的进步&#xff0c;非法闯入智能监测摄像机应运而生&#xff0c;成为保护家庭和财产安全的重要工具。这种摄像机不仅具备监控功能&#xff0c;还集成了智能识别和报警系统&#xff0c;能够在第一时间内检测到潜在的入侵行…

three.js创建基础模型

场景是一个三维空间&#xff0c;是所有物品的容器。可以将其想象成一个空房间&#xff0c;里面可以放置要呈现的物体、相机、光源等。 通过new THREE.Scene()来创建一个新的场景。 /**1. 创建场景 -- 放置物体对象的环境*/ const scene new THREE.Scene();场景只是一个三维的…

JVM(day2)

经典垃圾收集器 Serial收集 使用一个处理器或一条收集线程去完成垃圾收集工作&#xff0c;更重要的是强调在它进行垃圾收集时&#xff0c;必须暂停其他所有工作线程&#xff0c;直到它收集结束。 ParNew收集器 ParNew 收集器除了支持多线程并行收集之外&#xff0c;其他与 …

HTTP背后的故事:理解现代网络如何工作的关键(二)

一.认识请求方法(method) 1.GET方法 请求体中的首行包括&#xff1a;方法&#xff0c;URL&#xff0c;版本号 方法描述的是这次请求&#xff0c;是具体去做什么 GET方法&#xff1a; 1.GET 是最常用的 HTTP 方法. 常用于获取服务器上的某个资源。 2.在浏览器中直接输入 UR…

【实战:Django-Celery-Flower实现异步和定时爬虫及其监控邮件告警】

1 Django中集成方式一&#xff08;通用方案&#xff09; 1.1 把上面的包-复制到djagno项目中 1.2 在views中编写视图函数 1.3 配置路由 1.4 浏览器访问&#xff0c;提交任务 1.5 启动worker执行任务 1.6 查看任务结果 2 Django中集成方式二&#xff08;官方方案&#xff0…

25_Vision Transformer原理详解

1.1 简介 Vision Transformer (ViT) 是一种将Transformer架构从自然语言处理(NLP)领域扩展到计算机视觉(CV)领域的革命性模型&#xff0c;由Google的研究人员在2020年提出。ViT的核心在于证明了Transformer架构不仅在处理序列数据&#xff08;如文本&#xff09;方面非常有效&…

探索智能合约在金融科技中的前沿应用与挑战

随着区块链技术的发展和普及&#xff0c;智能合约作为其核心应用之一&#xff0c;在金融科技&#xff08;FinTech&#xff09;领域中展现出了巨大的潜力和挑战。本文将深入探讨智能合约的基本概念、前沿应用案例&#xff0c;以及面临的技术挑战和发展趋势&#xff0c;旨在帮助读…

redis笔记2

redis是用c语言写的,放不频繁更新的数据&#xff08;用户数据。课程数据&#xff09; Redis 中&#xff0c;"穿透"通常指的是缓存穿透&#xff08;Cache Penetration&#xff09;问题&#xff0c;这是指一种恶意或非法请求直接绕过缓存层&#xff0c;直接访问数据库或…

HouseCrafter:平面草稿至3D室内场景的革新之旅

在室内设计、房地产展示和影视布景设计等领域,将平面草稿图快速转换为立体的3D场景一直是一个迫切的需求。HouseCrafter,一个创新的AI室内设计方案,正致力于解决这一挑战。本文将探索HouseCrafter如何将这一过程自动化并提升至新的高度。 一、定位:AI室内设计的革新者 Ho…

通过MATLAB控制TI毫米波雷达的工作状态之TLV数据解析及绘制

前言 前一章博主介绍了如何基于设计视图中的这些组件结合MATLAB代码来实现TI毫米波雷达数据的实时采集。这一章将在此基础上实现TI毫米波雷达的TLV数据解析。过程中部分算法会涉及到一些简单的毫米波雷达相关算法,需要各位有一定的毫米波雷达基础。 TLV数据之协议解析 紧着…

爬虫学习 | 01 Web Scraper的使用

目录 背景介绍&#xff1a; 第一部分&#xff1a;Web Scraper简介 1.什么是Web Scraper&#xff1a; Web Scraper&#x1f6d2; 主要用途&#xff1a; 2.为什么选择Web Scraper&#xff1a; 第二部分&#xff1a;安装Web Scraper ​​​​​1.打开google浏览器&#xf…

实验六:频域图像增强方法

一、实验目的 熟练掌握频域滤波增强的各类滤波器的原理及实现。分析不同用途的滤波器对频域滤波增强效果的影响,并分析不同的滤波器截止频率对频域滤波增强效果的影响。二、实验原理 ① Butterworth 低通滤波器:一种具有最大平坦通带幅度响应的滤波器。它的特点是在通带内具…

WPF实现一个带旋转动画的菜单栏

WPF实现一个带旋转动画的菜单栏 一、创建WPF项目及文件1、创建项目2、创建文件夹及文件3、添加引用 二、代码实现2.ControlAttachProperty类 一、创建WPF项目及文件 1、创建项目 打开VS2022,创建一个WPF项目&#xff0c;如下所示 2、创建文件夹及文件 创建资源文件夹&…