Java使用类加载器解决类冲突,多版本jar共存

news2024/11/15 16:41:35

Java使用类加载器解决类冲突

  • 1、案例说明
  • 2、打包新版本POI并将要调用的方法封装
    • 2.1、POM文件
    • 2.2、封装的方法
  • 3、要使用多个POI版本的项目
    • 3.1、打包前面的项目生成一个jar包
    • 3.1、POM文件
    • 3.2、类加载器代码
    • 3.3、Jar加载工具
    • 3.4、最终调用

1、案例说明

项目中已经有了一个旧版本的poi库,并且这个库的版本无法修改,现在需要引入新版本的poi库,调用其中的公式方法IFS。之前想采用修改POI包名的方式,但是发现修改后各种报错无奈放弃。经过各种测试,本方法可以实现不同poi版本共存,因本人能力有限,部分代码可能写的不是最优,大家理解理解。
项目中真实包名啥的改成了xxxx,使用时主要改成正确的。

2、打包新版本POI并将要调用的方法封装

2.1、POM文件

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.xxxx</groupId>
	<artifactId>poicustom</artifactId>
	<version>2.0</version>
	<packaging>jar</packaging>

	<name>poicustom</name>
	<url>http://maven.apache.org</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>

	<dependencies>
		<!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml -->
		<dependency>
			<groupId>org.apache.poi</groupId>
			<artifactId>poi-ooxml</artifactId>
			<version>5.2.5</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-shade-plugin</artifactId>
				<executions>
					<execution>
						<goals>
							<goal>shade</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>
</project>

2.2、封装的方法

package com.xxxx.poicustom;

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.CellValue;
import org.apache.poi.ss.usermodel.FormulaEvaluator;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

public class PoiUtil {
	/**
	 * 评分公式计算
	 * @param formula 公式内容,其中目标值使用A1单元格,实际值使用B1单元格,权重使用C1单元格,结果会存储在D1单元格
	 * @param target 目标值
	 * @param actual 实际值
	 * @param weight 权重
	 * @return 返回不包含权重的得分
	 */
	public static double eval(String formula,double target,double actual,double weight) {
		Workbook workbook = null;
		try {
			//创建表格
			workbook = new XSSFWorkbook();
			//创建sheet页
			Sheet sheet = workbook.createSheet();
			//创建行
			Row row = sheet.createRow(0);
			//创建目标值
			Cell cellA1 = row.createCell(0, CellType.NUMERIC); // A1
			cellA1.setCellValue(target);
			//创建实际值
			Cell cellB1 = row.createCell(1, CellType.NUMERIC); // B1
			cellB1.setCellValue(actual);
			//创建权重
			Cell cellC1 = row.createCell(2, CellType.NUMERIC); // B1
			cellC1.setCellValue(weight);
			//创建结果
			Cell cellD1 = row.createCell(3, CellType.FORMULA); // C1
			cellD1.setCellFormula(formula); // 设置公式字符串
			
			// 评估公式以获取结果
			FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator();
			CellValue cellValue = evaluator.evaluate(cellD1);
			// 输出结果
			return cellValue.getNumberValue();
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			try {
				workbook.close();
			} catch (Exception e2) {
			}
		}
	}
}

3、要使用多个POI版本的项目

3.1、打包前面的项目生成一个jar包

打包前面的项目,生成一个jar包,并将jar包放在本项目指定的目录
在这里插入图片描述

3.1、POM文件

使用maven-resources-plugin插件将jar前面打包的jar包复制到对应的target目录,注意在本地运行项目前要先执行maven的compile,确保对应的jar包出现在target目录对应的位置

<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.7.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.xxxx</groupId>
	<artifactId>xxxx</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>xxxx</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>1.8</java.version>
		<lombok.version>1.18.12</lombok.version>
	</properties>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
	            <artifactId>maven-resources-plugin</artifactId>
	            <executions>
	                <execution>
	                    <id>copy-test-jar</id>
	                    <phase>process-resources</phase>
	                    <goals>
	                        <goal>copy-resources</goal>
	                    </goals>
	                    <configuration>
	                        <outputDirectory>${project.build.directory}/classes/com/xxx/poi</outputDirectory>
	                        <resources>
	                            <resource>
	                                <directory>${project.basedir}/src/main/java/com/xxx/poi</directory>
	                                <includes>
	                                    <include>**/*.jar</include>
	                                </includes>
	                            </resource>
	                        </resources>
	                    </configuration>
	                </execution>
	            </executions>
	        </plugin>
		</plugins>
	</build>

</project>

3.2、类加载器代码

直接复制过去,不用改

package com.xxxx;
import java.net.URL;
import java.net.URLClassLoader;

/**
 * jar类加载器
 */
public class CustomClassLoader extends URLClassLoader {

    public CustomClassLoader(URL[] urls, ClassLoader parent) {
        super(urls, parent);
    }

    public static CustomClassLoader createWithJars(ClassLoader parent,URL... jarUrl) throws Exception {
        return new CustomClassLoader(jarUrl, parent);
    }
}

3.3、Jar加载工具

本类主要实现从一个jar包中加载对应的类,并且此功能会将当前项目父类加载器传给自定义类加载器,确保加载的类和当前项目不冲突。
此功能会自动从多层jar包中解压jar包,并且会自动删除之前解压的jar包,但是项目停止时不会删除最后一次解压的jar包,有需要的可以按照需求修改。

package com.xxxx.extandjar;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * 扩展jar处理
 */
@Slf4j
public class ExtendJarUtil {
	
	//类加载器,每批jar使用一个新的类加载器加载
	private CustomClassLoader classLoader;
	
	/**
	 * 创建扩展jar处理工具
	 * @param jarPathArray
	 */
	public ExtendJarUtil(URL... urlArray) {
		try {
			log.info("------------------------原jar:"+JSONUtil.toJsonStr(urlArray));
			urlArray = getNoNestingUrlArray(urlArray);
			log.info("------------------------处理后jar:"+JSONUtil.toJsonStr(urlArray));
			ClassLoader parent = ExtendJarUtil.class.getClassLoader().getParent();
			classLoader = CustomClassLoader.createWithJars(parent,urlArray);
		} catch (Throwable e) {
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * 根据类名获取类
	 * @param name
	 * @return
	 * @throws ClassNotFoundException
	 */
	public Class<?> getClz(String name) throws ClassNotFoundException{
		Class<?> clz = classLoader.loadClass(name);
		return clz;
	}
	
	
	/**
	 * 获取一个不包含嵌套jar包的urlArray
	 * @param urlArray 包含jar包路径的urlArray,jar包路径可能会嵌套
	 * @return 包含jar包路径的urlArray,jar包路径不会嵌套
	 * @throws IOException
	 */
	public static URL[] getNoNestingUrlArray(URL[] urlArray) throws IOException{
		//将从urlArray读取到的实际url添加的urlList
		List<URL> urlList = new ArrayList<>();
		//读取当前目录下extra开头,jar结尾的文件并删除,因为每次会解压出一些临时文件
		File[] listFiles = new File(new File("").getAbsolutePath()).listFiles();
		if(listFiles!=null && listFiles.length>0) {
			for (File file : listFiles) {
				if(file.isFile() && file.getName().startsWith("extrajar") && file.getName().endsWith(".jar")) {
					FileUtil.del(file);
					log.info("删除jar:"+file);
				}
			}
		}
		
		//遍历url,获取最终代表的jar包
		for (URL url : urlArray) {
			//将url转成字符串
			String urlStr = url.toString();
			
			//如果路径中不包含!,则直接将当前url添加到urlList并返回
			if(!urlStr.contains("!")) {
				urlList.add(url);
				continue;
			}
			//如果路径中有!,则代表要获取的jar包含在某个jar中,这个可能是多层级嵌套,需要将最里面的jar解压到最外面并读取
			//如果是jar:开头则去掉这个
			if (urlStr.startsWith("jar:")) {
				urlStr = urlStr.substring(4, urlStr.length());
			}
			//如果是file:开头则去掉这个
			if (urlStr.startsWith("file:")) {
				urlStr = urlStr.substring(5, urlStr.length());
			}
			
			File jarFile = getJarFile(urlStr);
			urlList.add(jarFile.toURI().toURL());
		}
		URL[] array = ArrayUtil.toArray(urlList, URL.class);
		return array;
	}
	
	/**
	 * 从一个jar文件的url中获取最终的文件,如果有嵌套则解压获取
	 * @param jarUrlStr jar文件的url
	 * @return jar文件
	 * @throws IOException 
	 */
	public static File getJarFile(String jarUrlStr) throws IOException{
		//按照!分割,切割后按照顺序分别是每一个层级的文件,需要从第一层文件中获取第二层文件,然后从第二层文件中获取第三层文件
		String[] splitArray = jarUrlStr.split("!");
		//外层的jar
		File outFile = new File(splitArray[0]);
		//循环从外层jar解压内部jar
		for(int i=1;i<splitArray.length;i++) {
			//外层的jar
			JarFile outJarFile = new JarFile(outFile);
			//内层的jar路径
			String innerPath = splitArray[i];
			//去掉之前的/,防止相对路径读取文件错误
			if (innerPath.startsWith("/")) {
				innerPath = innerPath.substring(1, innerPath.length());
			}
			//获取内部jar的in
			InputStream innerIn = outJarFile.getInputStream(new JarEntry(innerPath));
			//设置一个临时文件,用来解压内部的jar
			File innerFile = new File("extrajar_"+UUID.randomUUID().toString(true)+".jar");
			//解压内部jar
			FileOutputStream innerOut = new FileOutputStream(innerFile);
            IoUtil.copy(innerIn,innerOut);
            
            //关闭资源
            try {
            	innerIn.close();
			} catch (Exception e) {
			}
            try {
            	innerOut.close();
			} catch (Exception e) {
			}
            try {
            	outJarFile.close();
			} catch (Exception e) {
			}
            //将解压的文件赋值到外层jar
            outFile = innerFile;
		}
		return outFile;
	}
	
	
}

3.4、最终调用

在项目中创建一个工具类,里面创建一个静态加载工具对象,并加载对应的jar包,然后写一个方法调用jar包中的方法。实现不同版本的POI隔离。

package com.xxxx.poi;

import java.lang.reflect.Method;

import com.xxxx.ExtendJarUtil;

/**
 * poi工具
 */
public class PoiUtil {
	
	/**
	 * 创建一个扩展jar处理
	 */
	private static final ExtendJarUtil poiJarUtil = new ExtendJarUtil(PoiUtil.class.getResource("poicustom-2.0.jar"));
	
	/**
	 * 获取公式计算的值
	 * @param formula 公式
	 * @param target 目标值
	 * @param actual 实际值
	 * @param weight 权重
	 * @return
	 */
	public static double eval(String formula,double target,double actual,double weight) {
		try {
			Class<?> clz = poiJarUtil.getClz("com.xxxx.PoiUtil");
        	Method method = clz.getMethod("eval",String.class,double.class,double.class,double.class);
            return (double)method.invoke(null,formula,target,actual,weight);
		} catch (Exception e) {
           throw new RuntimeException(e);
        }
	}
}

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

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

相关文章

【后端开发】PHP、go语言、Java、C++、Linux开发等急招中......

本周高薪急招后端开发岗位推荐&#xff0c;PHP、go语言、Java、C、Linux开发等岗位都在热招&#xff0c;月薪最高35K&#xff0c;还不快来&#xff01;&#xff01; 抓紧投递&#xff0c;早投早入职&#xff01; &#x1f447;点击职位名称查看详情&#x1f447; PHP 薪资&…

Leetcode每日刷题之102.二叉树的层序遍历

1.题目解析 本题是关于二叉树的层序遍历&#xff0c;不过这里的难点是如何将每一层的数据存储在数组并将整体存储在一个二维数组中&#xff0c;具体的算法原理我们在下面给出 2.算法原理 关于将每层数据分别存储在不同数组中&#xff0c;我们可以定义一个levelSize变量来存储栈…

网络编程(TCP+网络模型)

【1】TCP 初版服务器 #include <stdio.h> #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> #include <unistd.h> #include <arpa/inet.h> #include <string.h…

【学习笔记】SSL/TLS如何运用加密工具

一、前文回顾&#xff1a; 1、SSL/TLS有3个目的&#xff0c;分别由不同密码学工具提供 Confidentiality&#xff08;保密性&#xff09;&#xff1a;数据只有Client和Server才能访问&#xff0c;由Encryption&#xff08;加密&#xff09;所提供Integrity&#xff08;完整性&…

【话题讨论】VS Code:倍增编程动力,实现效率飞跃

目录 引言 一、详情介绍 功能特点 使用场景 提高工作效率 二、效率对比 2.1 高度可定制性与丰富的插件生态 2.2 智能的代码补全与导航 2.3 内置的调试器与版本控制集成 2.4 轻量级与跨平台 2.5 选择合适工具的重要性 2.6 实际案例或数据展示 三、未来趋势 3.1 编…

iOS——Block与内存管理

需要内存管理的情况 1、对象类型的auto变量。 2、引用了 __block 修饰符的变量。 三种block类型 全局类型 &#xff08;NSGlobalBlock&#xff09; 如果一个block里面没有访问普通局部变量(也就是说block里面没有访问任何外部变量或者访问的是静态局部变量或者访问的是全局…

FPGA开发:可编程逻辑器件概述

PLD 1、什么是PLD&#xff1f; PLD指Programmable Logic Device&#xff0c;翻译为"可编程逻辑器件"。是20世纪70年代发展起来的一种新的集成电路&#xff0c;是一种半定制的集成电路。 PLD具有逻辑功能实现灵活。集成度高、处理速度快的特点。 PLD就像是一个可定…

【Vue】pnpm创建Vue3+Vite项目

初始化项目 &#xff08;1&#xff09;cmd切换到指定工作目录&#xff0c;运行pnpm create vue命令&#xff0c;输入项目名称后按需安装组件 &#xff08;2&#xff09;使用vs code打开所创建的项目目录&#xff0c;Ctrl~快捷键打开终端&#xff0c;输入pnpm install下载项目…

IDEA运行Java程序提示“java: 警告: 源发行版 11 需要目标发行版 11”

遇到这个提示一般是在pom.xml中已经指定了构建的Java版本环境是11例如(此时添加了build插件的情况下虽然不能直接运行代码但是maven是可以正常打包构建)&#xff1a; <build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><…

Vue初学-简易计算器

最近在学习Vue的指令&#xff0c;做了一个简易计算器&#xff0c;比较适合刚入门的人参考学习。用到的知识点有&#xff1a; 1.插值表达式 2.v-model&#xff0c;双向绑定、-、*、/、**等操作符 3.v-show&#xff0c;控制操作数2是否显示&#xff0c;乘方时不显示操作数2 4.met…

‌软媒市场—‌软媒市场自助发布平台引领数字营销新风尚

在当今这个信息爆炸的时代,数字营销已经成为企业推广品牌、提升知名度的关键手段。而在众多数字营销工具中,‌软媒市场自助发布平台以其独特的优势脱颖而出,成为众多企业的首选。今天,我们就来深入探讨一下软文媒体自助发布平台如何在软媒市场中发挥重要作用,以及其背后的5万家…

FRP代理(TCP通信)实验

攻击机器---公网机器&#xff08;FRP服务端&#xff09;-TCP传输rdp内容--内网机器&#xff08;FRP客户端&#xff09;--内网本地&#xff08;RDP服务&#xff09; FRP版本&#xff1a;0.49.0 公网IP&#xff08;FRP服务端&#xff09;&#xff1a;192.168.254.131 内网&…

Mindspore 初学教程 - 4. 数据集 Dataset

数据是深度学习的基础&#xff0c;MindSpore 提供基于 Pipeline 的 数据引擎&#xff0c;通过数据集 数据集&#xff08;Dataset&#xff09; 和 数据变换&#xff08;Transforms&#xff09; 实现高效的数据预处理。其中 Dataset 是 Pipeline 的起始&#xff0c;用于加载原始数…

# centos7 安装 mysql

centos7安装mysql 1、添加 mysql 官方 yum 存储库 wget https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpmrpm -ivh mysql80-community-release-el7-3.noarch.rpm2、使用Yum安装MySQL服务器&#xff1a; sudo yum install mysql-server3、启动MySQL服务…

Redis集群技术2——redis基础

Redis安装 Redis 的安装相对简单&#xff0c;无论是 Windows、Linux 还是 macOS 系统&#xff0c;都有相应的安装方法。以下是针对不同操作系统的 Redis 安装简述。 1. Linux 系统安装 Redis 在 Linux 系统中安装 Redis 通常有多种方式&#xff0c;这里以 Ubuntu 和 CentOS 为…

配置阿里云千问大模型--环境变量dashscope

1 开通百炼 首先要进入到阿里云平台&#xff0c;然后进入百炼平台。 2 获取API-KEY 进入之后再右上角可以查看到自己的API-KEY&#xff0c;这个东西就是需要配置在环境变量里的。 点击查看就可以获取 3 配置DASHSCOPE环境变量 如果使用dashscope来进行千问大模型的API对…

速度滞后补偿控制

这里介绍的速度滞后补偿控制和我们前面介绍的前馈控制有所区别&#xff0c;前馈控制的前提是能够获取位置参考指令的速度或加速度信号。在无法获取位置参考指令的上述性息的前提下&#xff0c;我们可以采用速度滞后补偿控制提高机电伺服控制系统动态跟踪精度。前馈控制的一些基…

2024社区版IDEA springboot日志输出颜色

IDEA版本&#xff1a;IntelliJ IDEA 2024.1.4 (Community Edition) 1、纯白色终端 2、彩色终端 3、配置过程 1、打开配置 2、选择启动类 3、点击修改选项&#xff0c;勾选虚拟机选项 4、在虚拟机选项框输入以下代码 -Dspring.output.ansi.enabledALWAYS5、应用确定&#xff0…

NLP从零开始------18.文本中阶处理之序列到序列模型(3)

4.3 其他解码问题和解码技巧 贪心解码和束解码只是最基础的解码方法&#xff0c;其解码结果会出现许多问题。这里主要介绍3种常见问题&#xff0c;并简单介绍解决方案。 4.3.1 重复性问题 有时我们会发现序列到序列模型不断重复的输出同一个词。一个解决方案是解码时在所预测的…

GateWay三大案例组件

一、局部过滤器接口耗时&#xff08;LogTime&#xff09; 命名规则&#xff1a;以GatewayFilterFactory结尾编写接口耗时过滤器 Slf4j Component public class LogTimeGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {private static long timeSpan 0…