前言

对接不同公司时,由于用到了bouncycastle进行签名,都是其他公司封装好的jar包或方法依赖了bouncycastle系列的包,由于各种原因必须同时使用不同版本的包。

直接引用会报类重复加载的问题,由于java类加载使用双亲委派模型,同一个包名类名必定是同一个类加载器加载。java是当类名包名相同且类加载器相同时认为是同一个类,想要同时使用不同版本jar包只能通过自定义类加载器,破坏双亲委派模型来实现。

一 、Java类加载的过程

Java代码从编码完成到运行,包含两个步骤:

  • 编译:把写好的java文件通过javac命令编译成字节码(.class文件)。
  • 运行:把字节码文件交给JVM执行。

类加载的过程就是JVM把.class文件中类信息加载进内存,并解析生成class对象的过程。这个过程主要为3步:加载、链接、初始化,而链接可以分为3小步:验证、准备、解析,每个过程主要过程如下:

  1. 加载:把各个来源的的class字节码文件通过不同类加载器载入内存。
  2. 验证:保证加载进来的字节流符合虚拟机规范,不会造成安全规范。验证包括对于文件格式的验证,比如常量中是否有不被支持的常量?文件中是否有不规范的或者附加的其他信息?对于元数据的验证,比如该类是否继承了被final修饰的类?类中的字段,方法是否与父类冲突?是否出现了不合理的重载;对于字节码的验证,保证程序语义的合理性,比如要保证类型转换的合理性。对于符号引用的验证,比如校验符号引用中通过全限定名是否能够找到对应的类?校验符号引用中的访问性(private,public等)是否可被当前类访问?
  3. 准备:为变量分配内存,并且赋予初值,初值不是代码中的初始化的值而是根据不同变量设置默认值,其中引用类型为null。
  4. 解析:常量池内的符号引用替换为直接引用的过程。例如调用hello()方法,替换为方法的内存地址。
  5. 初始化:对static修饰的变量或语句进行初始化。

二、类加载器

  1. BootstrapClassLoader:启动类加载器,负责加载jre/lib/re.jar中的所有class。
  2. ExtensionClassLoader:标准扩展类加载器,负责加载jre/lib/ext中的所有class。
  3. AppClassLoader:系统类加载器,负责加载classpath中指定的jar包和目录中的class。
  4. CustomClassLoader:自定义类加载器。

三、双亲委派模型

双亲并没有特殊的含义,只是层级关系的称呼方式(女拳警告!)层级关系并不是继承的关系,而是组合,每个类加载器都有parent字段来定义上级。

java为了保证类只加载一次,类加载器加载类时,首先检查是否加载过,查不到就委派给上级,直到顶级的类加载器没有查到就加载。当加载的时候该顶级类会尝试加载,如果加载不了再交给下级加载。这样可以保证每个类在类加载器中只会加载一次。

下面是类加载器接口ClassLoader

protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException
{synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loadedClass<?> c = findLoadedClass(name); //查询是否加载过if (c == null) {long t0 = System.nanoTime();try {if (parent != null) {c = parent.loadClass(name, false); //没有加载过而且有上级就委派给上级} else {c = findBootstrapClassOrNull(name); //没有上级就交给顶级类加载器}} catch (ClassNotFoundException e) {// ClassNotFoundException thrown if class not found// from the non-null parent class loader}if (c == null) {// If still not found, then invoke findClass in order// to find the class.long t1 = System.nanoTime();c = findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}
}

自定义类加载器

自定义类加载器参考:https://blog.csdn.net/u011943534/article/details/89204709

package cn.gq.jdsk;import java.io.*;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;/*** @author chiangtaol* @date 2021-10-19* @describe*/
/*** 提供Jar隔离的加载机制,会把传入的路径、及其子路径、以及路径中的jar文件加入到class path。* 破坏双亲委派机制,改为逆向* */
public class JarLoader extends URLClassLoader {private static ThreadLocal<URL[]> threadLocal = new ThreadLocal<>();private URL[] allUrl;public JarLoader(String[] paths) {this(paths, JarLoader.class.getClassLoader());}public JarLoader(String[] paths, ClassLoader parent) {super(getURLs(paths), parent);//暂时先这样allUrl = threadLocal.get();}private static URL[] getURLs(String[] paths) {if (null == paths || 0 == paths.length) {throw new RuntimeException("jar包路径不能为空.");}List<String> dirs = new ArrayList<String>();for (String path : paths) {dirs.add(path);JarLoader.collectDirs(path, dirs);}List<URL> urls = new ArrayList<URL>();for (String path : dirs) {urls.addAll(doGetURLs(path));}URL[] urls1 = urls.toArray(new URL[0]);threadLocal.set(urls1);return urls1;}private static void collectDirs(String path, List<String> collector) {if (null == path || "".equalsIgnoreCase(path)) {return;}File current = new File(path);if (!current.exists() || !current.isDirectory()) {return;}for (File child : current.listFiles()) {if (!child.isDirectory()) {continue;}collector.add(child.getAbsolutePath());collectDirs(child.getAbsolutePath(), collector);}}private static List<URL> doGetURLs(final String path) {if (null == path || "".equalsIgnoreCase(path)) {throw new RuntimeException("jar包路径不能为空.");}File jarPath = new File(path);if (!jarPath.exists() || !jarPath.isDirectory()) {throw new RuntimeException("jar包路径必须存在且为目录.");}/* set filter */FileFilter jarFilter = new FileFilter() {@Overridepublic boolean accept(File pathname) {return pathname.getName().endsWith(".jar");}};/* iterate all jar */File[] allJars = new File(path).listFiles(jarFilter);List<URL> jarURLs = new ArrayList<URL>(allJars.length);for (int i = 0; i < allJars.length; i++) {try {jarURLs.add(allJars[i].toURI().toURL());} catch (Exception e) {throw new RuntimeException("系统加载jar包出错", e);}}return jarURLs;}//破坏双亲委派模型,采用逆向双亲委派@Overridepublic Class<?> loadClass(String name) throws ClassNotFoundException {if (allUrl != null) {String classPath = name.replace(".", "/");classPath = classPath.concat(".class");for (URL url : allUrl) {byte[] data = null;ByteArrayOutputStream baos = new ByteArrayOutputStream();InputStream is = null;try {File file = new File(url.toURI());if (file != null && file.exists()) {JarFile jarFile = new JarFile(file);if (jarFile != null) {JarEntry jarEntry = jarFile.getJarEntry(classPath);if (jarEntry != null) {is = jarFile.getInputStream(jarEntry);int c = 0;while (-1 != (c = is.read())) {baos.write(c);}data = baos.toByteArray();return this.defineClass(name, data, 0, data.length);}}}} catch (Exception e) {e.printStackTrace();} finally {try {if (is != null) {is.close();}baos.close();} catch (IOException e) {e.printStackTrace();}}}}return super.loadClass(name);}}
package cn.gq.jdsk;/*** @author chiangtaol* @date 2021-10-19* @describe*/
/**** 为避免jar冲突,比如hbase可能有多个版本的读写依赖jar包* 就需要脱离当前classLoader去加载这些jar包,执行完成后,又退回到原来classLoader上继续执行接下来的代码*/
public final class ClassLoaderSwapper {private ClassLoader storeClassLoader = null;private ClassLoaderSwapper() {}public static ClassLoaderSwapper newCurrentThreadClassLoaderSwapper() {return new ClassLoaderSwapper();}/*** 保存当前classLoader,并将当前线程的classLoader设置为所给classLoader** @param* @return*/public ClassLoader setCurrentThreadClassLoader(ClassLoader classLoader) {this.storeClassLoader = Thread.currentThread().getContextClassLoader();Thread.currentThread().setContextClassLoader(classLoader);return this.storeClassLoader;}/*** 将当前线程的类加载器设置为保存的类加载* @return*/public ClassLoader restoreCurrentThreadClassLoader() {ClassLoader classLoader = Thread.currentThread().getContextClassLoader();Thread.currentThread().setContextClassLoader(this.storeClassLoader);return classLoader;}
}

测试代码如下

@Test
public void classloader() throws Exception{String jar1 = "/Users/chiangtaol/Downloads/test/jd/jar1"; //自己定义的测试jar包,不同版本打印内容不同String jar2 = "/Users/chiangtaol/Downloads/test/jd/jar2";JarLoader jarLoader = new JarLoader(new String[]{jar1});ClassLoaderSwapper classLoaderSwapper = ClassLoaderSwapper.newCurrentThreadClassLoaderSwapper();classLoaderSwapper.setCurrentThreadClassLoader(jarLoader);Class<?> aClass = Thread.currentThread().getContextClassLoader().loadClass("cn.tnt.bean.TestClass");classLoaderSwapper.restoreCurrentThreadClassLoader();Object o = aClass.newInstance();Method isEmptyMethod = aClass.getDeclaredMethod("hello");Object invoke = isEmptyMethod.invoke(o);System.out.println(invoke);JarLoader jarLoader2 = new JarLoader(new String[]{jar2});ClassLoaderSwapper classLoaderSwapper2 = ClassLoaderSwapper.newCurrentThreadClassLoaderSwapper();classLoaderSwapper2.setCurrentThreadClassLoader(jarLoader2);Class<?> aClass2 = Thread.currentThread().getContextClassLoader().loadClass("cn.tnt.bean.TestClass");classLoaderSwapper.restoreCurrentThreadClassLoader();Object o2 = aClass2.newInstance();Method isEmptyMethod2 = aClass2.getDeclaredMethod("hello");Object invoke2 = isEmptyMethod2.invoke(o2);System.out.println(invoke2);
}
/*** @author chiangtaol* @date 2021-10-20* @describe*/
public class TestClass {public String hello(){return SoutUtil.sout();}
}//为了测试引用是否会加载正确,分为两个类打印
/*** @author chiangtaol* @date 2021-10-20* @describe*/
public class SoutUtil {public static String sout(){System.out.println("这是jar包33333");return "333";}
}

结果如下:

这是jar包1111111
111
这是jar包22222
222

自定义的类主要针对必须同时引用两个不同版本的同名jar包时的问题解决思路。

java同时引用不同版本同一个jar包相关推荐

  1. spring2.0和spring2.5及以上版本的jar包区别 spring jar 包详解

    spring jar 包详解 spring.jar是包含有完整发布的单个jar包,spring.jar中包含除了 spring-mock.jar里所包含的内容外其它所有jar包的内容,因为只有在开发环 ...

  2. java9 多版本兼容jar_Java 9 多版本兼容 jar 包

    Java 9 多版本兼容 jar 包 多版本兼容 JAR 功能能让你创建仅在特定版本的 Java 环境中运行库程序时选择使用的 class 版本. 通过 --release 参数指定编译版本. 具体的 ...

  3. 【工作笔记】如何在MySQL官网下载Java连接MySQL数据库的驱动jar包

    我们在开发时,Java连接MySQL数据库需要驱动jar包,而驱动包的版本要跟MySQL数据库版本相匹配,以下介绍怎样在MySQL官网下载各MySQL数据库对应版本的驱动包. 1.登陆Oracle官网 ...

  4. 单纯的Java项目打成一个可运行jar包或者普通依赖包

    写一个Java工具打成可运行jar包提供使用,或者普通依赖包. 目录 写一个Java工具打成可运行jar包提供使用,或者普通模块包. 方案一.打出的jar包里结果比较乱 方案二.依赖jar包打到外面, ...

  5. java项目导入包报错_转!java web项目 build path 导入jar包,tomcat启动报错 找不到该类...

    在eclipse集成tomcat开发java web项目时,引入的外部jar包,编译通过,但启动tomcat运行web时提示找不到jar包内的类,需要作如下配置,将jar包在部署到集成的tomcat环 ...

  6. .net core 引用jar_Python一键转Jar包,Java调用Python新姿势!

    粉丝朋友们,不知道大家看故事看腻了没(要是没腻可一定留言告诉我^_^),今天这篇文章换换口味,正经的来写写技术文.言归正传,咱们开始吧! 今天的这篇文章,聊一个轩辕君之前工作中遇到的需求:如何在Jav ...

  7. java org.apache.http_org.apache.http jar包下载-org.apache.http.jar包下载 --pc6下载站

    org.apache.http.jar包是一款十分常用的jar包如果没有org.apache.http.jar包Apache与http的链接将会出现错误等现象马上下载org.apache.http.j ...

  8. (转)利用Ant与Proguard混淆引用的子工程项目jar包及打war包

    当前的web项目有引用到子工程项目,而且多个子工程项目也有引用到其它的工程项目,现要求利用Ant自动将web项目打包成war包,其中引用到的子工程项目需打成jar包,而且必须是混淆后的jar包.其中混 ...

  9. maven引用公共包_Maven项目怎样引用其他项目/或者jar包-阿里云开发者社区

    惯例: 我是温浩然: 现在越来越多的项目都用Maven进行管理jar包,我这里说的是,Maven怎样引用本地Maven项目. 一个项目从SVN上下载下来,先在本地删除(只是在工作环境中删除,不要删除文 ...

最新文章

  1. 为什么ORM性能比iBATIS好?
  2. Java 技术篇-用java自带的内存检测工具排查内存泄漏问题,查看java垃圾回收情况,监控java堆内存变化
  3. mysql更新数据索引慢_mysql添加索引,查询反而变慢
  4. win7自带tftp服务器,Win7系统怎么开启tftp服务器?Win7开启tftp服务器操作方法
  5. SharePoint 2007 Select People and Groups中搜索不到其他Domain账户的问题[已解决]
  6. cassandra使用心得_避免在Cassandra中使用清单
  7. python为什么没有数据类型_python3 数据类型
  8. 享元模式在文本编辑器中的应用
  9. 搭建 WordPress 个人博客(阅读文档)
  10. LREC'22 | 机器翻译中细粒度领域自适应的数据集和基准实验
  11. 前W3C顾问Klaus Birkenbihl谈HTML5与万维网未来
  12. Spark中使用Dataset的groupBy/agg/join/broadcast hasjoin/sql broadcast hashjoin示例(java api)
  13. 密码学基础(二):对称加密
  14. maven 打包跳过test的命令
  15. html怎么实现年月日的选择,利用select实现年月日三级联动的日期选择效果【推荐】...
  16. 计算机网络发展史及分类,计算机网络发展史简介
  17. python闰月计算_Python实例讲解 -- 获取本地时间日期(日期计算)
  18. 品牌制造商做电子商务的成本结构分析
  19. 神经网络模型如何应用到实际 - 神经网络模型数学建模案例
  20. oracle移动文件命令是什么意思,Oracle使用命令移动各类文件的方法

热门文章

  1. Microsoft JDBC Driver XX (XX表示版本号)for SQL Server的安装
  2. [译] part 13: golang 映射 map
  3. ipad分屏功能怎么开启_win10怎么开启投屏功能
  4. JAVA程序把大写转换小写_Java程序将字符串转换为小写和大写。
  5. python floor是什么意思_简单介绍Python中的floor()方法
  6. 讯飞语音--唤醒Demo
  7. Oracle在命令行中输入clear,Oracle SQLPlus 常用命令及解释
  8. Oracle10g ora12170,ORA-3136、TNS-12535 12170 12606
  9. 微信公众平台-测试号-测试接口的问题
  10. 2019云计算公司排名 哪家的云服务器最好用?