分享
 
 
 

通过ClassLoader管理组件依赖(图)

王朝other·作者佚名  2008-05-31
窄屏简体版  字體: |||超大  

java的类加载机制是非常强大的。你可以利用外部第三方的组件而不需要头文件或静态连接。你只需要简单的把组件的JAR文件放到classpath下的目录中。运行时引用完全是动态处理的。但假如这些第三方组件有自己的依靠关系时会怎么样呢?通常这需要开发人员自己解决所有需要的相应版本的组件集,并且确认他们被加到classpath中。

JAR清单文件

实际上你不需要这样做,Java的类加载机制可以更优雅地解决这个问题。一种方案是需要每一个组件的作者在JAR清单中定义内部组件的依靠关系。这里清单是指一个被包含在JAR中的定义文件元数据的文本文件(META-INF/MANIFEST.MF)。最常用的属性是Main-Class,定义了通过java –jar方式定位哪个类会被调用。然而,还有一个不那么有名的属性Class-Path可以用来定义他所依靠的其他JAR。Java缺省的ClassLoader会检查这些属性并且自动附加这些特定的依靠到classpath中。

让我们来看一个例子。考虑一个实现交通模拟的Java应用,他由三个JAR组成:

·simulator-ui.jar:基于Swing的视图来显示模拟的过程。

·simulator.jar:用来表示模拟状态的数据对象和实现模拟的控制类。

·rule-engine.jar:常用的第三方规则引擎被用来建立模拟规则的模型。

simulator-ui.jar依靠simulator.jar,而simulator.jar依靠rule-engine.jar。

而通常执行这个应用的方法如下:

$ java -classpath

simulator-ui.jar:simulator.jar:rule-engine.jar

com.oreilly.simulator.ui.Main

编者注:上面的命令行应该在同一行键入;只是由于网页布局的限制看起来似乎是多行。

但我们也可以在JAR的清单文件中定义这些信息,simulator-ui的MANIFEST.MF如下:

Main-Class: com.oreilly.simulator.ui.Main

Class-Path: simulator.jar

而simulator的MANIFEST.MF包含:

Class-Path: rule-engine.jar

rule-engine.jar或者没有清单文件,或者清单文件为空。

现在我们可以这样做:

$ java -jar simulator-ui.jar

Java会自动解析清单的入口来取得主类及修改classpath,甚至可以确定simulator-ui.jar的路径和解释所有与这个路径相关的Class-Path属性,所以我们可以简单按照下面的方式之一来做:

$ java -jar ../simulator-ui.jar

$ java -jar /home/don/build/simulator-ui.jar

依靠冲突

Java的Class-Path属性的实现相对于手工定义整个classpath是一个大的改善。然而,两种方式都有自己的限制。一个重要的限制就是你只能加载组件的一个特定版本。这看起来是很显然的因为许多编程环境都有这个限制。但是在大的包含多个第三方依靠的多JAR项目中依靠冲突是很常见的。

例如,你正在开发一个通过查询多个搜索引擎并比较他们的结果的搜索引擎。Google和Amazon的Alexa都支持使用SOAP作为通讯机制的网络服务API,也都提供了相应的Java类库方便访问这些API。让我们假设你的JAR- metasearch.jar,依靠于google.jar和amazon.jar,而他们都依靠于公共的soap.jar。

现在是没有问题,但假如将来SOAP协议或API发生改变时会怎么样呢?很可能这两个搜索引擎不会选择同时升级。可能在某一天你访问Amazon时需要SOAP1.x版本而访问Google时需要SOAP2.x版本,而这两个版本的SOAP并不能在同一个进程空间中共存。在这里,我们可能包含下面的JAR依靠:

$ cat metasearch/META-INF/MANIFEST.MF

Main-Class: com.onjava.metasearch.Main

Class-Path: google.jar amazon.jar

$ cat amazon/META-INF/MANIFEST.MF

Class-Path: soap-v1.jar

$ cat google/META-INF/MANIFEST.MF

Class-Path: soap-v2.jar

上面正确地描述了依靠关系,但这里并没有包含什么魔法--这样设置并不会像我们期望地那样工作。假如soap-v1.jar和soap-v2.jar定义了许多相同的类,我们肯定这是会出问题的。

$ java -jar metasearch.jar

SOAP v1: remotely invoking searchAmazon

SOAP v1: remotely invoking searchGoogle

你可以看到,soap-v1.jar被首先加在classpath中,因此实际上也只有他会被使用。上面的例子等价于:

$ java -classpath

metasearch.jar:amazon.jar:google.jar:soap-v1.jar:soap-v2.jar

# WRONG!

编者注:上面的命令行应该在同一行键入;只是由于网页布局的限制看起来似乎是多行。

有趣的是假如Yahoo也发布了一个网络服务API,而他看起来并没有依靠于现有的SOAP/xml-RPC类库。在较小的项目中,组件依靠冲突常被用来作为在你只要手工包装方案或者只需要一两个类时而不使用让你不使用全量组件(如集合类库)的原因之一。手工包装方案有他的用处,但使用已有的组件是更普遍的方式。而且复制其他组件的类到你的代码库永远不是一个好主意。实际上你已经与组件的开发产生分岐而且没有机会在有问题修复或安全升级时合并他。

许多大的项目,如主要的商业组件,已经采用将他们使用的整个组件构建到他们的JAR内部。为了这么做,他们改变了包名使其唯一(如com/acme/Foobar/org/freeware/utility),而且直接在他们的JAR中包含类。这样做的好处是可以防止在这些组件中多个版本的冲突,但这也是有代价的。这么做对开发人员来说完全隐藏了对第三方的依靠。但假如这种方式大规模的应用,将会导致效率的降低(包括JAR文件的大小和加载多个JAR版本到进程中的效率降低)。这种方式的问题在于假如两个组件依靠于同一个版本的第三方组件时,就没有协调机制来确定共享的组件只被加载一次。这个问题我们会在下一节进行研究。除了效率的降低外,很可能你这种绑定第三方软件的方式会与那些软件的许可协议冲突。

另一种解决这个问题的方式是每一个组件的开发人员显式的在他的包名中编码一个版本号。Sun的javac代码就采用这个方式—一个com.sun.tools.javac.Main类会简单地转发给com.sun.tools.javac.v8.Maino。每次一个新的Java版本发布,这个代码的包名就改变一次。这就答应一个组件的多个发布版本可以共存在同一个类加载器中并且这使得版本的选择是显式的。但这也不是一个非常好的解决方案,因为或者客户需要准确知道他们计划使用的版本而且必须改变他们的代码来转换到新的版本,或者他们必须依靠于一个包装类来转发方案调用给最新的版本(在这种情况下,这些包装类就会承受我们上面提到的相同问题)。

加载多个发布版本

这里我们碰到的问题在大多数项目中也存在,所有的类都会被加载到一个全局命名空间。假如每一个组件有自己的命名空间而且他会加载所有他依靠的组件到这个命名空间而不影响进程的其他部分,那又会怎么样呢?实际上我们可以在Java中这么做!类名不需要是唯一的,只要类名和其所对应的ClassLoader的组合是唯一的就可以了。这意味着ClassLoader类似于命名空间,而假如我们可以加载每一个组件在自己的ClassLoader中,他就可以控制如何满足依靠。他可以代理类定位给其他的包含他的依靠组件所需要的特定版本的ClassLoader。如图1。

Figure 1. Decentralized class loaders

然而这个架构并不比绑定每一个依靠的JAR在自己的JAR中好多少。我们需要的是一个可以确保每一个组件版本仅被一个类加载器加载的中心集权。图2中的架构可以确定每一个组件版本仅被加载一次。

Figure 2. Class loaders with mediator

为了实现这种方式,我们需要创建两个不同类型的类加载器。每一个ComponentClassLoader需要扩展Java的URLClassLoader来提供需要的逻辑来从一个JAR中获取.class文件。当然他也会执行两个其他的任务。在创建的时候,他会获取JAR清单文件并定位一个新属性Restricted-Class-Path。不像Sun提供的Class-Path属性,这个属性暗示特定的JAR应该只对这个组件有效。

public class ComponentClassLoader extends URLClassLoader {

// ...public ComponentClassLoader (MasterClassLoader master, File file)

{

// ...JarFile jar = new JarFile(file);

Manifest man = jar.getManifest();

Attributes attr = man.getMainAttributes();

List l = new ArrayList();

String str = attr.getValue("Restricted-Class-Path");

if (str != null) {

StringTokenizer tok = new StringTokenizer(str);

while (tok.hasMoreTokens()) {

l.add(new File(file.getParentFile(),

tok.nextToken());

}

}

this.dependencies = l;

}public Class loadClass (String name, boolean resolve)

throws ClassNotFoundException{

try {

// Try to load the class from our JAR.

return loadClassForComponent(name, resolve);

} catch (ClassNotFoundException ex) {}

// Couldn't find it -- let the master look for it

// in another components.

return master.loadClassForComponent(name,

resolve, dependencies);

}

public Class loadClassForComponent (String name,

boolean resolve)

throws ClassNotFoundException

{

C

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有