克服类加载器混乱

计算机考研考博经典考题汇总(一次刷新世界观-我相信VIP总是有原因的) 操作系统 操作系统的特点? – 共享:资源可被多个并发执行的进程使用 – 并发:可以在同一时间间隔处理多个进程,需要硬件支持 – 虚拟:将物理实体映射成为多个虚拟设备 – 异步:进程执行走走停停,每次进程执行速度可能不同,但OS需保证进程每次执行结果相同 进程的三个组成部分? 程序段、数据段、PCB(Process Control Block) 并发与并行区别? 并发:同一间隔 并行:同一时刻 进程切换的过程? 保持处理机上下文 -> 更新PCB -> 把PCB移入相应队列(就绪、阻塞) -&g 阅读详情

ClassLoader解决方案只需要投入一次成本,它提供了一个解决类版本冲突的方法

  最近,我不断听到同事和熟人抱怨J2EE应用服务器中出现的软件版本冲突。这个基础问题由来已久,但是,随着应用程序与应用服务器之间共享的Java库日益增多,这个问题似乎也越来越严重。当应用服务器使用一个Java包的A版本,而位于这台服务器上的应用程序却使用这个包的B版本时,如果这两个版本不兼容,那么就会产生版本冲突。当应用程序试图使用这个包,系统加载的是版本A中的类,而不是B版本中的类。如果这两种类的行为不同,就会出现问题。
  这种情况相当普遍,部分原因是因为如此多的应用服务器都在某种程度上依靠于开源软件。商业性软件的发布周期通常没有开源软件的发布周期那么短。因此,新发布的应用服务器经常不包含一些库的最新版本。另外,企业软件升级周期又落后于供应商的发布周期,而且为了保持稳定性,有时候也会跳过发布周期。结果,开发人员想当然地使用最新最好的Java库,一头扎进组合更旧版本的一些库的J2EE服务器中。
  这个问题也会出现在其他方向上。在升级应用服务器时,多年来一直使用的企业应用软件可能会遇见兼容问题。如果程序依靠于与应用服务器打包在一起的旧版本的库,那么在程序试图访问一个不存在的API元素时,新的库会引发运行时异常,比如NoSuchMethodException。
  很多时候,程序员没有注意到正在使用的是不同版本的库(这里,我认为类是Java包的集合),因为他们没有使用已经变化了的那一部分API,或者没有引起在后来版本中得到修正的缺陷。问题一旦发生,开发人员就不得不想办法解决它。替换应用服务器库会使应用服务器或者该服务器上的其他应用程序中断。如果开发人员没有对应用服务器的管理控制权,那么这个解决方案根本不可行。

共享和类共享
  这个问题的症结在于大多数J2EE供应商为他们的产品设计了一个类加载层次结构,这个层次结构最终会把类加载委托给应用服务器的类加载程序。即使给每个Web应用程序指派单独的类加载程序,防止Web应用程序彼此干涉,服务器本身使用的库仍然可以被所有Web应用程序共享。我想肯定一些产品没有这个问题,但是我听到的有关报道说,这个问题几乎出现于大多数主要的开放源代码产品和封闭源代码产品上。
  编程人员用来解决版本冲突的常用方法是:将库的源代码中的包的名称改成只由其自己代码使用的惟一名称,重新编译库,并将所有导入语句及其引用更新为其源代码中的包的名称。这个解决方案只在访问库的源代码时起作用,然而并不是总是如此。尽管源代码修正和重新编译是一个短期可靠的解决方案,从长远角度来看,实际上它花费的时间更长。编写shell脚本或使用IDE插件能够使包自动重新命名。
  查找并替换源代码中出现的所有包的名称,这样将捕获包名称在包声明和导入语句之外的地方的使用情况,比如说,在使用完全限定类名称时,或有在用于反射的字符串中插入名称时。配置文件和其他支持文件都可以包含名称,并且反射中使用的一些名称可以自动生成。您不必总是仔细地检查代码来重新命名某一个名称的所有实例。更重要的是,当升级到新版本时,必须重复一遍整个过程。基于以前的修改的补丁文件不会重新命名出现在新版本中的所有包的名称,并且shell脚本可能要求进行更新来应对代码更改。
  最后,源代码修正为维护带来了一个难题。您要花费人手和时间来维护一个原本不需要维护的东西,它实际上是源代码树的一个独立分支。
  源代码修正的两个主要缺陷是:必须访问源代码,而维护源代码需要做相当多的工作。维护一个单独的库的分支看起来似乎不是十分困难,但是那些想解决这个问题的人必须解决与多个库的冲突。
源代码包重名命名的一个替换解决方案是重写二进制类文件。重写类文件有一个好处:不需要维护一个单独的源代码分支,也不需要维护源代码。惟一需要的是JAR文件。专门重新命名JAR文件中的包的工具很少,但是大多数代码混淆工具(code obfuscation tool)都有重新命名包和JAR文件中包含的类的能力。用这个方法可以使库的升级变得容易一些。您所要做的就是用重写工具处理JAR文件,然后就大功告成。

想做便做!
  尽管类文件重写看上去似乎很有效,但实际上这还不是一个完美的解决方案。当库使用反射以及在字符串或配置文件中嵌入包的名称时,这种方法不起作用。它还不能把您从更改应用程序源代码中包名称的使用方式的不懈努力中解脱出来。
  一个更全面的解决方案是做一些应用服务器供应商应该首先作的事情,然后通过使用一个自定义类加载器把您的库的副本与服务器的库的副本分离开。要做到这一点,必须编写一些额外的代码,但是不必改变现有源文件使用包名称的方式。库升级变得简单是因为您只需使用新的JAR文件取代旧的文件即可。如何做到这一点的呢?
  版本冲突的根源是应用服务器的类加载设计。Web应用程序类加载器在试图自己定位一个类之前,把类的加载委托给了这个类的父类加载器。因此,如果应用服务器的类加载器能够在系统位置上找到这个类,那么它会加载那个版本,而不是加载和Web应用程序一起打包的那个版本。如果使用您自己的没有父类的类加载器来引导应用程序,那么您就可以绕过应用服务器使用的库。
  作为这项技巧的一个例子,我定义了一个叫做Printer的接口和一个叫做VersionPrinter的实现类,这个类表示一个应用程序。  VersionPrinter依靠于Version类,但是需要特定的5.0.0版本。然而,应用服务器用的是1.0.2版本。因此,在调用VersionPrinter.print时,就会输出字符串“version: 1.0.2”。

哪一个版本?
清单1. VersionPrinter使用一个新的5.0.0版本的Version,但是应用服务器装载的是老的1.0.2版本。

            public interface Printer {
            public void print();
}

public class VersionPrinter implements Printer {
            public void print() {
    Version v = new Version();
    System.out.println("version: " + v.getVersion());
   }
}

public class Version {
  public String getVersion() {
    return "1.0.2";
  }
}

public class Version {
  public String getVersion() {
    return "5.0.0";
  }
}

自定义类加载
清单2. 使用自定义类加载器和一个动态代理,您可以绕过应用服务器的类路径。

package example;

import java.io.*;
import java.net.URL;
import java.net.URLClassLoader;
import java.lang.reflect.*;

public final class Main {

  static class PrinterInvoker implements 
    InvocationHandler {
    Object adaptee;

    public PrinterInvoker(Object adaptee) {
      this.adaptee = adaptee;
    }

    public Object invoke(
      Object proxy, Method method, Object[] args)
      throws Throwable
    {
      Method adapteeMethod =
        adaptee.getClass().getMethod(
        method.getName(),
         method.getParameterTypes());
      if(!adapteeMethod.isAccessible())
        adapteeMethod.setAccessible(true);
      return adapteeMethod.invoke(adaptee, args);
    }
  }

  public static final void main(String[] args)
    throws Exception
  {
    VersionPrinter vp = new VersionPrinter();

    vp.print();

    // hardcoded demo paths from build.xml
    File path1 =
      new File(System.getProperty("user.dir"),
      "build.src2");
    File path2 =
      new File(System.getProperty("user.dir") , 
      "build.src");
    URL[] classpath = new URL[] {
      path1.toURL(), path2.toURL()
    };

    URLClassLoader cl = new URLClassLoader(
      classpath, null);
    Object obj =
      cl.loadClass(
      "example.VersionPrinter").newInstance();
    Printer p = 
      (Printer)Proxy.newProxyInstance(
        Printer.class.getClassLoader(),
        new Class[] { Printer.class },
        new PrinterInvoker(obj));

    p.print();
  }
}

      通过定义一个类加载器,可以绕过应用服务器的库,这个类加载器在查看服务器库目录之前,会首先查看自己的库目录。通过把两个Version类放进两个不同的构建目录中,并先在类加载路径中放置了包含Version类的5.0.0版本的目录(参见清单2),我模拟了这个类加载器。接着,我创建了一个URLClassLoade实例,并用自定义路径和一个空的父类对其进行初始化。空的父类可以确保类的加载不会委托给父类。然后,我加载了这个类,并使用一个动态代理把它映射到一个已知接口。在运行这个示例程序时,直接调用VersionPrinter.print将输出“version: 1.0.2”,而动态代理调用将输出“version: 5.0.0”,这些输出结果显示了想要使用的类版本,而不是默认版本。
  使用例子中的技巧,您根本不必更改应用程序代码。有时,编程人员会自己加载一些类似Version的特殊类,但也许您不想那样做。如果打算这样,您将不得不更改VersionPrinter。这样,就必须通过反射访问每一个冲突类。那会使代码变得一团糟。您想做是:建立一个由接口定义的应用程序入口点(比如,Printer),并自定义加载那个应用程序。然后,自定义类加载器将加载这个应用程序使用的所有更深层的类。

 

一次性购买
  实现一个能够用自定义类路径和委派servlet(delegate servlet)配置的包装器servlet是有可能的。包装器 servlet将使用这个自定义类路径来加载委派的servlet,并将所有调用委派给那个委派servlet。不幸的是,一些应用服务器中的servlet方法需要访问由应用服务器类加载器加载的资源。因此,包装器servlet技术不能保证在所有情况下都有效。您仍然可以使用实现一个选择性地将类加载委派给父类的类加载器的技巧。在从特定包中加载类时,可以将类加载器配置成不将类加载委派给父类。
  类加载器解决方案所需的额外努力是一个缺点,但是该解决方案的花费是一次性的。动态代理的使用应该不会降低性能,只要您在离主应用入口点尽可能近的地方使用它即可,这样会最大程度地减少反射性方法调用的数量。加载的类将消耗额外的内存,但那是为在相同JVM中使用同一个类的不同版本付出的代价。一些新版的J2EE服务器可能提供了他们自己的版本冲突解决方案。至少我想起来有一台服务器重新命名了它所使用的包,因此,您不必重新命名这些表包。不过,即使现在遇见类版本冲突,您也已经有了一个摆脱困境的方法。

关于作者
Daniel F. Savarese是一名独立软件开发人员和技术顾问。他曾是ORO公司的创始人,Caltech高级计算处理中心的高级科学家和WebOS软件开发的副总裁。Daniel是Jakarta ORO 文本处理包和Jakarta Commons NET网络协议库的原始作者.他还是《How to Build a Beowulf》(MIT Press, 1999)一书的合著者之一。

原文出处
http://www.ftponline.com/channels/java/javapro/2005_03/magazine/columns/proshop/

FOC电机控制中电流采样延迟补偿的原理和方法 摘要: FOC电机控制中,电流采样、计算与PWM更新存在固有延迟(约1.5个PWM周期),导致相位滞后,限制电流环带宽。主流补偿方法包括: 一阶前向预测法:基于历史电流值预测未来电流,抵消延迟,工程应用广泛; 相位超前补偿:通过s/z域校正器设计实现等效预测; 状态观测器法(如龙伯格观测器):通过电机模型闭环估计状态,兼具噪声抑制和参数辨识能力,但复杂度高。实际需注意参数敏感性、离散化精度及延迟时间校准,避免过补偿风险。一阶预测法因平衡效果与复杂度成为工业首选。 阅读详情

相关推荐

VSCode玩转STM32:手把手教你配置EIDE插件与OpenOCD调试(附野火DAP配置文件详解)

本文详细介绍了如何在VSCode中配置EIDE插件与OpenOCD进行STM32开发,特别针对野火DAP调试器提供了配置文件详解。从基础环境搭建到深度调试技巧,帮助开发者高效完成STM32项目开发,实现现代化嵌入式开发工作流。

weixin_26905487的博客 553

初识java类的加载机制

双亲委派/类的加载/沙箱机制

m0_55852693的博客 314

HFSS入门学习笔记——微带一分四功分器的设计与仿真

功率分配器是将输入信号功率分成相等或不相等的几路功率输出的一种多端口无源微波网络,用于功率分配和功率合成。工程上常用的微带功分器有T型功分器和威尔金森功分器。经典的威尔金森功分器有几个缺点:①、多应用于低频段,高频段时,波长会与隔离电阻的尺寸相比拟,此时需要考虑电阻的分布参数效应;②、大功率应用时,隔离电阻的耗散功率要很大,因此隔离电阻的体积也会比较大。

weixin_42126696的博客 2万+

java 类加载两次,Java ClassLoader:加载相同的类两次

I have a ClassLoader which loads a class compiled by JavaCompiler from a source file.But when I change the source file, save it and recompile it, the ClassLoader still loads the first version of the c...

weixin_39572288的博客 634

QDMA工程搭建/windows驱动编译与性能测试

请注意,根据您的目标操作系统,您可能需要在windows启动配置中启用测试签名的驱动程序,以便启用此驱动程序的安装。右键单击设备,选择更新驱动程序软件,然后选择构建的QDMA驱动程序的文件夹(通常为。创建一个新工程,在IP Catalog中搜索QDMA,双击创建qdma ip核。驱动程序安装安装驱动程序最简单的方法是通过Windows的“设备管理器”。如果系统提示未经验证的驱动程序发布者,请选择无论如何安装此驱动程序软件。本节主要介绍QDMA驱动在Windows系统的源文件、编译和安装过程。

qq_28455081的博客 2287

unity中判断是否联网,判断网络状态的两种方式

unity中判断是否联网,判断网络状态的两种方式

wla2996gpy032的博客 1876

Arthas分析类被TongWebURLClassLoader和ThanosTomEEWebappClassLoader两次加载问题

TongWeb7041部署应用后,用Arthas诊断工具分析类加载情况会发现某些类被加载了两次。被多次加载的类主要是应用中使用到的servlet类和实现Filter接口的类。......

ghostYang的专栏 846

Android-S无线模块前瞻

WiFi - mainlane非强制 S 采用反射的方法直接加载wifiservice,直接hook在systemserver上,无编译依赖。gms可以动态替换wifi服务。 R 采用直接启动service的方式,wifiservice仍然map在systemserver上。 WiFi - 11ad接口支持 11ad被wifi联盟商业化为WIGIG,提供短距的60GHZ载波,理论速度为当前5G 11ac的6倍。市面上基本没有手机支持,需要独立的芯片支撑此功能。 传输距离为3m以内,外界d.

头铁的伦的博客 1044

Java类加载器(类加载的流程、三大类加载器BootstrapClassLoader、ExtClassLoader、AppClassLoader)

BootstrapClassLoader BootstrapClassLoader是顶级加载器,默认加载的是%JAVA_HOME%中lib下的jar包和class类文件,他也是ExtClassLoader的父类,但是不是继承(extends)关系,是ExtClassLoder中有一个parent变量是BootstrapClassLoader ExtClassLoader ExtClassLoader扩展类加载器,负责加载%JAVA_HOME%中lib/ext文件下的jar包和class类文件,ExtClas

qq_42623400的博客 1万+

机器视觉检测到底怎么做,经验分享

笔者做机器视觉检测已经有五六年了,也算是对机器视觉的检测技术有了一定的掌握。总的来说,基于机器视觉的检测技术主要包含三个部分:视觉采集模块、后台分析模块和动作决策模块。这几个分类,跟教科书上的有较大差异,很多同学觉得我在杜撰。笔者也不作反驳,因为这是笔者这些年工作经验的总结,自然区别于教科书。下面我们分开来讲这几个模块,同时会把笔者之前的论文做个推荐总结。 所有文章均可在博主的主页中找到,若无法找到,可以用搜索工具。博主的主页: https://blog.youkuaiyun.com/Aoulun 第..

Aoulun的博客 8677

Android系统修改驱动固定USB摄像头节点绑定前后置摄像头

Android系统中usb摄像头节点会因为摄像头所接的usb口不同或者usb设备识别顺序不一样而出现每次开机生成的video节点不一样的问题。由于客户app调用摄像头时,需要固定摄像头的节点。因此需要针对前面的情况做处理。

Android系统开发笔记 2251

【转】克服类加载器混乱

ClassLoader解决方案只需要投入一次成本,它提供了一个解决类版本冲突的方法   最近,我不断听到同事和熟人抱怨J2EE应用服务器中出现的软件版本冲突。这个基础问题由来已久,但是,随着应用程序与应用服务器之间共享的Java库日益增多,这个问题似乎也越来越严重。当应用服务器使用一个Java包的A版本,而位于这台服务器上的应用程序却使用这个包的B版本时,如果这两个版本不兼容,那么就会...

weixin_30649859的博客 86

如何给数据添加高斯白噪声?

首先测试集应该是加噪声的,至于训练集需不需要加噪声就见仁见智了。我个人测试的结果是,训练集如果不加噪声的话效果特别差,如果加噪声的话效果就比较好接受,大家可以自己试试看。在轴承故障研究中免不了要涉及到给数据主动加入噪声的情况,一般我们加的是高斯白噪声。由于热噪声是由大量自由电子的运动产生的,其统计特性服从高斯分布,故常将热噪声称为高斯白噪声。的频谱是均匀分布的,好像白光的频谱在可见光的频谱范围内均匀分布那样,所以热噪声又常称为。定义一:如果一个噪声,它的瞬时值服从。又是均匀分布的,则称它为高斯白噪声。

weixin_50642818的博客 5220

JAVA 自动生成cad_可以编写代码的代码:代码生成的利与弊

可以编写代码的代码:代码生成的利与弊代码生成的当前状态代码生成的当前状态是无处不在的(2019年春季)。如今,代码生成发生在软件堆栈的每一层,包括Java库(如swagger CodeGen),最新的交叉编译器/编译器(如针对JavaScript应用程序的Babel)以及全栈生成器(如Starter StackGen(tm))REST API的数量激增,导致在过去十年中针对各种编程语言和环境开发的...

weixin_39924481的博客 377

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

第九十九期:可以编写代码的代码:代码生成的利与弊

代码生成的当前状态是无处不在的(2019年春季)。REST API的数量激增,导致在过去十年中针对各种编程语言和环境开发的API客户端生成器种类繁多。 作者:约翰·麦克马洪 代码生成的当前状态 代码生成的当前状态是无处不在的(2019年春季)。 如今,代码生成发生在软件堆栈的每一层, 包括Java库(如swagger CodeGen), 最新的交叉编译器/编译器(如针对Jav...

歌谣的博客 584

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

代码生成器连接不上mysql8.0_可以编写代码的代码:代码生成的利与弊

代码生成的当前状态代码生成的当前状态是无处不在的(2019年春季)。如今,代码生成发生在软件堆栈的每一层,包括Java库(如swagger CodeGen),最新的交叉编译器/编译器(如针对JavaScript应用程序的Babel)以及全栈生成器(如Starter StackGen(tm))REST API的数量激增,导致在过去十年中针对各种编程语言和环境开发的API客户端生成器种类繁多。就像某种...

weixin_39926042的博客 394

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

java 类加载,包冲突

JVM类生命周期 编译器将 Robot.java 编译成字节码文件 Robot.class ClassLoader 将 Robot.class 转换成 JVM 中的 Class 对象 JVM 使用 Class 对象生成 Robot 实例 类何时被加载 类加载是一个按需的过程。 遇到 new,getstatic,putstatic,invokestatic 这四个字节码指令时,若此时类还没有被初...

justsomebody126的专栏 1294

自定义类加载器加载冲突类(一)-ClassLoader

使用场景:项目中遇到使用不同版本的类时候因为同路径同名类的冲突可以使用自定义类加载器进行加载规避这一问题 相关代码如下: 1. 类加载器: import java.io.File; import java.io.FileInputStream; import java.io.IOException; /** * @Description TODO * @Date 2020/3/10 ...

pp_lan的博客 1453

理解 Linux 动态库加载:LD_LIBRARY_PATH

flyfish

二分掌柜的 379

0.4 文件与文件夹:数据是怎么存放的,一篇讲清目录树

电脑里几万个文件怎么归类?本文讲清文件是有名字的数据单位、文件夹(目录)是可层层嵌套的收纳容器,二者共同构成目录树,最顶层是根目录;建立字节/KB/MB/GB 的大小直觉,介绍 Linux 一切皆文件的设计;用一张表讲清复制、移动、重命名、删除的区别,以及 Delete 进回收站、Shift+Delete 永久删除的差异;最后从安全视角讲文件上传(伪装脚本上传即 WebShell)与路径穿越(ZipSlip 把文件写到敏感位置),提醒凡是文件读写都要警惕路径被操纵。

一个安全白帽子的成长记录:Web 安全、漏洞复现、内网渗透。完整目录在 gysec.top 224

【Linux网络】TCP 服务器并发处理多客户端:从 fork 分发到双重 fork 彻底消灭僵尸进程

拆解 TCP 服务器并发演进链:fork 分发连接、SIGCHLD 循环回收僵尸、双重 fork 交孙进程给 init 收养,并给出模型选型与线上排错对照。

2301_78967866的博客 429

LangGraph 本地 build 频频翻车,最终在服务器跑通流式对话

先说结论:我本地那个,一次都没成功过。三次,三次,全挂在拉镜像上。而顺得像刚上过油。这就很像我高中那辆自行车:骑着能走,一上锁就坏。那天晚上我盯着终端里那行红字看了很久,想的不是技术方案,想的是——为什么同一个工具,可以一边让我觉得自己很行,一边让我觉得自己不太行。

人工智能AI技术 119

【Linux网络】TCP 服务器如何并发处理多个客户端?从 fork、僵尸进程到双重 fork

本文详解多进程TCP服务器的实现原理,从单进程并发瓶颈出发,通过fork分离连接接收与服务职责。重点解析文件描述符继承机制、僵尸进程问题及双重fork解决方案,最终构建高效、稳定的多进程服务器模型,并说明服务器重启时可能因端口未释放导致绑定失败的原因。

2402_87731470的博客 2406

VSCode_RemoteSSH_一直卡在下载服务器_解决教程

解决VSCode远程一直卡在下载服务器

m0_69395553的博客 107

自建 Git 服务器和裸仓库差在哪:要能提合并请求

在一台 Linux 上 git init --bare 就能自建 Git 服务器,但它只能推拉代码,没有网页、合并请求、评审和按仓库分配的权限。对照裸仓库和 AngusGit 各自能做什么,再走一遍在 AngusGit 上发起合并请求的最短路径,以及已有裸仓库怎么完整搬过来。

AngusTester 325
上一篇: HTTP 1.1状态代码以及它们对应的状态信息和含义
下一篇: Java类加载内幕
DeepNightTwo
博客等级 码龄21年 3粉丝 25原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值