Java中的SPI机制与扫描class原理

文章目录

  • 前言
  • ClassLoader
  • JAVA SPI机制
  • Spring SPI机制
    • 示例
    • 原理
  • 如何加载jar包里的class

前言

Java的SPI机制与Spring中的SPI机制是如何实现的?

ClassLoader

这里涉及到了class Loader的机制,有些复杂,jdk中提供默认3个class Loader:

  • Bootstrap ClassLoader:加载jdk核心类库;加载%JAVA_HOME\lib%下的jar;
  • ExtClassLoader:加载jdk扩展类库;加载%JAVA_HOME\lib\ext%下的jar;
  • AppClassLoader:加载classpath下的class,以及关联到maven仓库里的jar;

AppClassLoaderExtClassLoader父类都是URLClassLoader,我们自定义也是继承URLClassLoader进行扩展的;

所以,当我们使用类加载器加载资源时,它会找上面这些路径,而AppClassLoader是找当前执行程序的classpath,也就是我们target/classes目录,如果有是maven引用了其他依赖包,那么也会将maven地址下的依赖包的路径加到AppClassLoaderURL里,如果是多模块的项目,还会把引用的其他模块下target/classes的目录也加进来。

image-20230804144522488

image-20230804144419885

JAVA SPI机制

Java中提供的SPI机制是通过读取META-INF/services/目录下的接口文件,从而加载到实现类。

其规则如下:

  1. 规定号开放api
  2. 实现提供方需要依赖开发接口完成实现,例如msyql
  3. 实现提供方,resource下提供META-INF/services/接口全名文件,内容为实现类

例如下面这个:

image-20230713104226856

重现建一个项目app用来测试

  1. 定义接口plugin-api打成jar

    /**
     * @author ALI
     * @since 2023/6/30
     */
    public interface Plugin {
    
        Object run(Object data);
    }
    
    
  2. 定义实现,然后打成jar

    /**
     * @author ALI
     * @since 2023/6/30
     */
    public class PluginImpl implements Plugin {
        @Override
        public Object run(Object data) {
            Motest motest = new Motest();
            System.out.println(motest.getName());
            System.out.println(data);
            return null;
        }
    }
    
    /**
     * @author ALI
     * @since 2023/6/30
     */
    public class Motest {
        private String name;
    
        public Motest() {
            name = "sss";
        }
    
        public String getName() {
            return name;
        }
    }
    
    

    这里我还定义了一个其他的类,用来测试再load class时是否会加载。

  3. 在新项目中加载jar中的资源,引入plugin-api

       /**
         * 使用jar的classLoader
         */
        private static void load2() throws Exception{
            String jarPath = "E:/workspace/git/test-plugin/app/target/classes/plugin-impl-1.0-SNAPSHOT.jar";
            URLClassLoader jarUrlClassLoader = new URLClassLoader(new URL[]{new URL("file:" + jarPath)});
            // ServerLoader搜索
            ServiceLoader<Plugin> load = ServiceLoader.load(Plugin.class, jarUrlClassLoader);
            Iterator<Plugin> iterator = load.iterator();
            while (iterator.hasNext()) {
                // 实例化对象:这里会进行加载(Class.forName),然后反射实例化
                Plugin next = iterator.next();
                next.run("sdsdsdsds");
            }
        }
    

    这里使用ServiceLoader时传入了jarClassLoader,开篇已经解释过了:因为类加载器的原因,不会加载我们自定义的jar包,所以手动创建类加载器。

    image-20230713144008469

    结果已经很显而易见,已经成功加载了,这种方式的划,会加载jar包里实现了接口的所有实现类,这个方式使用也是很方便的。

  4. 使用URLClassLoader加载class

Spring SPI机制

在Spring中,它的SPI机制,和JAVA 中的类似,需要这样的条件:

  1. 定义接口模块包,用于开发给第三方实现;

  2. 第三方要有resources\META-INF\spring.factories文件,其内容是键值对方式,key为接口类,value就是我们的实现类;

而Spring执行就是获取到文件里的value,然后反射实例化。

示例

  1. 定义接口模块

image-20230804152730928

  1. 定义第三方实现组件,并配置spring.factoryies

    image-20230804152135550

  2. 项目中引入接口模块组件,和实现组件

    image-20230804152923183

    结果:

    image-20230804153102826

原理

loadFactories两个参数

Class factoryType:用于反射实例化;

ClassLoader classLoader:用于加载资源,所有这里可以直接使用URLClassLoader指定jar的类加载,如果不指定,就是它自己本身的类加载;

	public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
		Assert.notNull(factoryType, "'factoryType' must not be null");
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
            // 如果为空,它用自己的加载器
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
        // 这里就是加载spring.factories文件里的value值
        // 找出所有的实现类的类路径
		List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
		if (logger.isTraceEnabled()) {
			logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
		}
		List<T> result = new ArrayList<>(factoryImplementationNames.size());
        // 遍历找出来的类,然后通过反射实例化
		for (String factoryImplementationName : factoryImplementationNames) {
			result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
		}
        // 排序
		AnnotationAwareOrderComparator.sort(result);
		return result;
	}

这里看一下

	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        // 将接口类转化成类路径,如com.liry.pluginapi.Plugin
		String factoryTypeName = factoryType.getName();
        // 先获取到spring.factories里的键值对(map),然后再get
		return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
	}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    // 缓存;程序运行中需要多次获取
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
            // 通过类加载获取所有资源地址url
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
            // 遍历
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
                // 通过PropertiesLoaderUtils工具获取spring.factories里的键值对
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
                    // 将value通过逗号分隔成数组,然后再全部添加到结果集中
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
            // 加入缓存
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

注意:MultiValueMap这个map相同的key不会覆盖value,而是组成链表,如下,一个key可以有多个value,逗号分隔

	public void add(K key, @Nullable V value) {
		List<V> values = this.targetMap.computeIfAbsent(key, k -> new LinkedList<>());
		values.add(value);
	}

如何加载jar包里的class

假设需要获取一个jar包里的class该如何?

如下4个步骤即可:

    public static void main(String[] args) throws Exception {

        String packageName = "com.liry.springplugin";
        // 1. 转换为 com/liry/springplugin
        String packagePath = ClassUtils.convertClassNameToResourcePath(packageName);

        // 2. 通过类加载器加载jar包URL
//        ClassLoader classLoader = Test.class.getClassLoader();
        ClassLoader classLoader = new URLClassLoader(new URL[]{new URL("file:E:\\workspace\\git\\test-plugin\\spring-plugin\\target\\spring-plugin-1.0-SNAPSHOT.jar")});
        URL resources = classLoader.getResource(packagePath);

        // 3. 打开资源通道
        JarFile jarFile = null;
        URLConnection urlConnection = resources.openConnection();
        if (urlConnection instanceof java.net.JarURLConnection) {
            java.net.JarURLConnection jarURLConnection = (java.net.JarURLConnection) urlConnection;
            jarFile = jarURLConnection.getJarFile();
        }

        // 定义一个结果集
        List<String> resultClasses = new ArrayList<>();

        // 4. 遍历资源文件
        Enumeration<JarEntry> entries = jarFile.entries();

        while (entries.hasMoreElements()) {
            JarEntry entry = entries.nextElement();
            // 文件全路径
            String path = entry.getName();
            // 判断是否在指定包路径下,jar包里有多层目录、MF文件、class文件等多种文件信息
            if (path.startsWith(packagePath)) {
                // 使用spring的路径匹配器匹配class文件
                if (path.endsWith(".class")) {
                    resultClasses.add(path);
                }
            }
        }
        resultClasses.forEach(System.out::println);
    }

image-20230803174544910

说明一下,加载jar包的问题;

上面给出了两种方式

第一种:使用类加载加载

ClassLoader classLoader = Test.class.getClassLoader();

第二种:使用URLClassLoader加载

ClassLoader classLoader = new URLClassLoader(new URL[]{new URL("file:E:\\workspace\\git\\test-plugin\\spring-plugin\\target\\spring-plugin-1.0-SNAPSHOT.jar")});

这两种方式不同之处在于,查找jar的路径,第一种方式因为我测试项目使用的maven,在pom.xml里引入了spring-plugin-1.0-SNAPSHOT的包,所以才能通过类加载器直接进行加载,这是因为使用maven,maven引用的依赖路径会被加入到AppClassLoader种,然后使用Test.class.getClassLoader()去加载class时,会委派给AppClassLoader进行加载,才会加载到。

所以,如果不是在maven种引入的包,使用第二种方式。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Hive/Spark/Yarn: User Not Found 错误和 Kerberos / AD / OpenLDAP 之间的关系与解释

有时候,当你向Spark或Hive提交作业时,可能会遇到如下的错误: 提交作业使用的用户是example-user-1,但是Yarn返回的错误信息是:该用户不存在。类似的问题大多发生在启动了Kerberos的Hadoop集群上,或者集群集成了Windows AD或OpenLDAP后。本文,我们把这个问题梳理清楚并给…

AOP获取切点表达式中注解的属性

文章目录 1、获取Cacheable注解的属性2、获取自定义注解的属性 1、获取Cacheable注解的属性 有个小需求&#xff0c;要在日志中打印支持redis缓存的方法的相关信息&#xff0c;这里切点表达式动词用annotation&#xff0c;后面跟Cacheable注解 Component Aspect Slf4j public…

【Pytorch】下载CIFAR10数据集报错: urllib.error.URLError: <urlopen error name: https>

在使用Pytorch 下载CIFAR10的时候&#xff0c;遇到一个报错&#xff0c; 可能是网络特别慢导致的&#xff0c;一般情况下都会遇到这个报错。 解决办法&#xff1a; 1、到官网直接下载这个压缩包&#xff0c;解压。 http://www.cs.utoronto.ca/~kriz/cifar.html 解压后&#x…

2023华数杯数学建模C题思路分析 - 母亲身心健康对婴儿成长的影响

# 1 赛题 C 题 母亲身心健康对婴儿成长的影响 母亲是婴儿生命中最重要的人之一&#xff0c;她不仅为婴儿提供营养物质和身体保护&#xff0c; 还为婴儿提供情感支持和安全感。母亲心理健康状态的不良状况&#xff0c;如抑郁、焦虑、 压力等&#xff0c;可能会对婴儿的认知、情…

性能监控工具-Grafana安装和使用方法

Grafana是一款开源的数据可视化和监控平台。它提供了丰富的可视化方式&#xff0c;如图表、仪表盘、警报等&#xff0c;支持多种数据源&#xff0c;包括Prometheus、InfluxDB、Graphite等&#xff0c;适用于各种规模的系统监控和数据分析。Grafana还有一个强大的插件生态系统&a…

自然语言处理学习笔记(一)————概论

目录 1.自然语言处理概念 2.自然语言与编程语言的比较 &#xff08;1&#xff09;词汇量&#xff1a; &#xff08;2&#xff09;结构化&#xff1a; &#xff08;3&#xff09;歧义性&#xff1a; &#xff08;4&#xff09;容错性&#xff1a; &#xff08;5&#xff0…

【Jmeter】压测mysql数据库中间件mycat

目录 背景 环境准备 1、下载Jmeter 2、下载mysql数据库的驱动包 3、要进行测试的数据库 Jmeter配置 1、启动Jmeter图形界面 2、加载mysql驱动包 3、新建一个线程组&#xff0c;然后如下图所示添加 JDBC Connection Configuration 4、配置JDBC Connection Configurati…

2023华数杯C题完整模型代码

华数杯C题完整论文模型代码已经完成&#xff0c;文末获取&#xff01; 母亲的心理健康状况对婴儿的成长和发展有重要的影响。本研究使用大数据分析方法&#xff0c;探索了母亲的心理健康状况、婴儿的行为特征以及婴儿的睡眠质量之间的相关性。我们采集了大量的数据&#xff0c;…

ad+硬件每日学习十个知识点(22)23.8.2(LDO datasheet手册解读)

文章目录 1.LDO的概述、features2.LDO的绝对参数&#xff08;功率升温和结温&#xff09;3.LDO的引脚功能4.LDO的电气特性5.LDO的典型电路&#xff08;电容不能真用1uF&#xff0c;虽然按比例取输出值&#xff0c;但是R2的取值要考虑释放电流&#xff09;6.LDO的开关速度和线性…

Dubbo+Zookeeper使用

说明&#xff1a;Apache Dubbo 是一款 RPC 服务开发框架&#xff0c;用于解决微服务架构下的服务治理与通信问题&#xff0c;官方提供了 Java、Golang 等多语言 SDK 实现。 本文介绍Dubbo的简单使用及一些Dubbo功能特性&#xff0c;注册中心使用的是ZooKeeper&#xff0c;可在…

QT图形视图系统 - 使用一个项目来学习QT的图形视图框架 - 始篇

文章目录 QT图形视图系统介绍开始搭建MainWindow框架设置scene的属性缩放功能的添加加上标尺 QT图形视图系统 介绍 详细的介绍可以看QT的官方助手&#xff0c;那里面介绍的详细且明白&#xff0c;需要一定的英语基础&#xff0c;我这里直接使用一个开源项目来介绍QGraphicsVi…

mysql高级(尚硅谷-夏磊)

目录 内容介绍 Linux下MySQL的安装与使用 Mysql逻辑架构 Mysql存储引擎 Sql预热 索引简介 内容介绍 1、Linux下MySQL的安装与使用 2、逻辑架构 3、sql预热 Linux下MySQL的安装与使用 1、docker安装docker run -d \-p 3309:3306 \-v /atguigu/mysql/mysql8/conf:/etc/my…

一百四十二、Linux——查看Linux服务器架构的版本类型

一、目的 查看已经安装好的Linux服务器架构的版本类型&#xff0c;看服务器版本是32位还是64位 而且可以区分出是kettle的文件x86或x86_64&#xff0c;x86是32位&#xff0c;而x86_64是64位 注意&#xff1a; 32位的查询结果为i386、i686 64位的查询结果为x86_64 二、Linu…

Java阶段五Day20

Java阶段五Day20 文章目录 Java阶段五Day20项目推进完成订单OrderServerService && MessageTransSenderRepositoryMessageTransRepoImplLocalTransactionLisetner 结算订单业务流程图远程调用 画时序图 项目推进 完成订单 通过分布式消息事务解决本地事务和发消息的一…

python+django+mysql项目实践三(用户管理)

python项目实践 环境说明: Pycharm 开发环境 Django 前端 MySQL 数据库 Navicat 数据库管理 用户列表展示 urls view models html <!DOCTYPE html> <html

MySQL 重置root 密码

5.7 版本 首先要把服务mysql57 关闭 net stop MySQL57 在安装的mysql57的程序的bin中 运行cmd&#xff08;管理员运行&#xff09; mysqld --defaults-file‘mysql存放数据的位置\my.ini’ --skip-grant-tables 上图 错误 注意&#xff1a;如果遇到mysqld: Can’t change dir…

答辩PPT怎么做?在线PPT软件哪个好?

又是一年毕业季&#xff0c;相信很多毕业生都开始准备论文答辩&#xff0c;有些同学正在为论文奋夜苦战&#xff0c;有些则是为论文答辩PPT而烦恼。做PPT要用什么软件好呢&#xff1f;这篇文章就来告诉你。 当下有很多PPT制作工具&#xff0c;其中自然也包括Office三件套。这些…

vue- form动态表单验证规则-表单验证

前言 以element官网的form表单的-动态增减表单项为例讲解表单验证规则 动态的功能就是v-model配合push v-for 便利来实现的 我们需要熟知2个知识点prop表单验证需要跟v-model绑定的值是一样的&#xff0c; 如果是一个数组便利的表单&#xff0c;那就需要绑定这个数组每一项…

【基础类】—CSS盒模型的全面认识

一、基本概念&#xff1a;标准IE模型 盒模型&#xff1a;margin border padding content 标准模型&#xff1a;将元素的宽度和高度仅计算为内容区域的尺寸&#xff08;content-box&#xff0c;默认&#xff09; 当CSS盒模型为 标准盒模型 &#xff08;box-sizing: conten…

交通运输安全大数据分析解决方案

当前运输市场竞争激烈&#xff0c;道路运输企业受传统经营观念影响&#xff0c;企业管理者安全意识淡薄&#xff0c;从业人员规范化、流程化的管理水平较低&#xff0c;导致制度规范在落实过程中未能有效监督与管理&#xff0c;执行过程中出现较严重的偏差&#xff0c;其营运车…