1、第2章 JBoss JMX微内核(Microkernel)2.2 JBoss JMX实现架构2.2.1 JBoss类装载器架构JBoss 3.x实现了一种新的类装载架构,即允许类跨部署单元使用。在JBoss 2.x中,很难实现MBean服务和动态部署的J2EE组件进行交互,并且MBean本身不具有热部署能力。在JBoss 3.x中,任何东西都是热部署的,因为新的部署架构和类装载架构使得它们成为可能。本文在深入讨论具体的JBoss类装载模型之前,将给出Java的类型系统特性及类装载器介绍。2.2.2 类装载和Java中的类型类装载是所有服务器架构的基础组成部分。具体服务及其支持服务的类必须装载到
2、服务器框架中。Java的强类型化(typed)特性使得类装载易于出现问题。大部分开发者知道,Java中类的类型是由类的全限定(fully qualified)名决定的。从Java 1.2开始,用于定义类的java.lang.ClassLoader也能够决定类的类型。这样做的理由在于确保从特定位置装载类的环境是类型安全的(type-safe)。Vijay Saraswat于1997年发表的论文“Java is not type-safe 译者注:论文全文请参考”证明了Java不是类型安全的。这使得应用能够使用先前装载的其他类实现版本而愚弄JVM,从而访问到Java不应该访问的类方法和成员。这种对
3、类型系统的欺骗的根源在于这些应用引入了那些能够跨越正常委派模型的类装载器。类装载器使用委派模型搜索类和资源。在通常情况下,类装载器实例都有与之关联的双亲类装载器,它可能是在创建类装载器时显式设置的;如果没有给出双亲类装载器,则由JVM指定。如果需要寻找类或资源,则在类装载器自身去寻找它们之前,通常情况下都会将该搜索工作委派给其双亲类装载器。JVM有根类装载器,称为引导(bootstrap)类装载器。引导类装载器没有双亲类装载器,但能够成为其他类装载器实例的双亲。为了解决类型安全问题,Java类型系统除了通过类名完整地定义类型外,还引入了用于定义类的类装载器。其内容请参考由Sheng Liang
4、和Gilad Bracha完成的论文Dynamic Class Loading in the Java Virtual Machine。同时,通过Web地址 papers/oopsla98.ps.gz,开发者能够获得其原文。另外,在动态环境中,比如应用服务器(尤其是支持热部署的JBoss),动态类装载方式有了更深入的发展。其中,ClassCastException、LinkageError及IllegalAccessError更能展示出静态类装载上下文中所看不到的场景。本文接下来仔细研究上述各种异常的具体含义和发生方式。1ClassCastException我不是你的类型无论在什么情况下,只要
5、将实例造型(cast)作为其不兼容的类型,系统就会抛出java.lang.ClassCastException异常。比如,某简单实例如下:将.URL放入java.util.ArrayList后,试图获得java.lang.String的代码和异常信息如下:ArrayList array = new ArrayList();array.add(new URL(file:/tmp);String url = (String) array.get(0);java.lang.ClassCastException: .URLat org.jboss.chap2.ex0.ExCCEa.main(Ex1CC
6、E.java:16)开发者可以从ClassCastException看出,将array元素造型作为String类型失败,因为该元素的实际类型为.URL。当然,开发者对这种试验性的场景不会感兴趣。让我们考虑另一种场景:不同的URLClassLoader装载了相同的jar文件。从字节码角度考虑,尽管通过不同URLClassLoader装载的类是完全相同的,但它们被Java类型系统看做完全不同的类型。列表2-1、列表2-2和列表2-3给出了相应的代码实例。列表2-1 用于证明由于不同类装载器而触发ClassCastException的ExCCEc类1 package org.jboss.chap2.
7、ex0;23 import java.io.File;4 import .URL;5 import .URLClassLoader;6 import java.lang.reflect.Method;78 import org.apache.log4j.Logger;910 import org.jboss.util.ChapterExRepository;11 import org.jboss.util.Debug;1213 /* An example of a ClassCastException that results from classes loaded through14 * d
8、ifferent class loaders.15 * author Scott.Starkjboss.org16 * version $Revision:$17 */18 public class ExCCEc19 20 public static void main(String args) throws Exception21 22 ChapterExRepository.init(ExCCEc.class);2324 String chapDir = System.getProperty(chapter.dir);25 Logger ucl0Log = Logger.getLogger
9、(UCL0);26 File jar0 = new File(chapDir+/j0.jar);27 ucl0Log.info(jar0 path: +jar0.toString();28 URL cp0 = jar0.toURL();29 URLClassLoader ucl0 = new URLClassLoader(cp0);30 Thread.currentThread().setContextClassLoader(ucl0);31 Class objClass = ucl0.loadClass(org.jboss.chap2.ex0.ExObj);32 StringBuffer b
10、uffer = new StringBuffer(ExObj Info);33 Debug.displayClassInfo(objClass, buffer, false);34 ucl0Log.info(buffer.toString();35 Object value = objClass.newInstance();3637 File jar1 = new File(chapDir+/j0.jar);38 Logger ucl1Log = Logger.getLogger(UCL1);39 ucl1Log.info(jar1 path: +jar1.toString();40 URL
11、cp1 = jar1.toURL();41 URLClassLoader ucl1 = new URLClassLoader(cp1);42 Thread.currentThread().setContextClassLoader(ucl1);43 Class ctxClass2 = ucl1.loadClass(org.jboss.chap2.ex0.ExCtx);44 buffer.setLength(0);45 buffer.append(ExCtx Info);46 Debug.displayClassInfo(ctxClass2, buffer, false);47 ucl1Log.
12、info(buffer.toString();48 Object ctx2 = ctxClass2.newInstance();4950 try51 52 Class types = Object.class;53 Method useValue = ctxClass2.getMethod(useValue, types);54 Object margs = value;55 useValue.invoke(ctx2, margs);56 57 catch(Exception e)58 59 ucl1Log.error(Failed to invoke ExCtx.useValue, e);6
13、0 throw e;61 62 63 列表2-2 实例使用到的ExCtx类64 package org.jboss.chap2.ex0;6566 import java.io.IOException;6768 import org.apache.log4j.Logger;6970 import org.jboss.util.Debug;7172 /* A classes used to demonstrate various class loading issues73 * author Scott.Starkjboss.org74 * version $Revision: 1.2 $75 *
14、/76 public class ExCtx77 78 ExObj value;7980 public ExCtx() throws IOException81 82 value = new ExObj();83 Logger log = Logger.getLogger(ExCtx.class);84 StringBuffer buffer = new StringBuffer(ctor.ExObj);85 Debug.displayClassInfo(value.getClass(), buffer, false);86 log.info(buffer.toString();87 ExOb
15、j2 obj2 = value.ivar;88 buffer.setLength(0);89 buffer = new StringBuffer(ctor.ExObj.ivar);90 Debug.displayClassInfo(obj2.getClass(), buffer, false);91 log.info(buffer.toString();92 93 public Object getValue()94 95 return value;96 97 public void useValue(Object obj) throws Exception98 99 Logger log =
16、 Logger.getLogger(ExCtx.class);100 StringBuffer buffer = new StringBuffer(useValue2.arg class);101 Debug.displayClassInfo(obj.getClass(), buffer, false);102 log.info(buffer.toString();103 buffer.setLength(0);104 buffer.append(useValue2.ExObj class);105 Debug.displayClassInfo(ExObj.class, buffer, fal
17、se);106 log.info(buffer.toString();107 ExObj ex = (ExObj) obj;108 109 void pkgUseValue(Object obj) throws Exception110 111 Logger log = Logger.getLogger(ExCtx.class);112 log.info(In pkgUseValue);113 114 115列表2-3 实例使用到的ExObj和ExObj2类package org.jboss.chap2.ex0;import java.io.Serializable;/* author Sco
18、tt.Starkjboss.org* version $Revision:$*/public class ExObj implements Serializablepublic ExObj2 ivar = new ExObj2();-package org.jboss.chap2.ex0;import java.io.Serializable;/* author Scott.Starkjboss.org* version $Revision: 1.1$*/public class ExObj2 implements SerializableExCCEc.main方法使用反射将应用程序的类装载器
19、与URLClassLoader ucl0和ucl1各自装载的类隔离开,而这些类都是从output/chap2/j0.jar文件中装载的。j0.jar的具体内容如下:nrtoki examples$ jar -tf output/chap2/j0.jar.org/jboss/chap2/ex0/ExCtx.classorg/jboss/chap2/ex0/ExObj.classorg/jboss/chap2/ex0/ExObj2.class本文将通过运行实例来证明ClassCastException是如何出现的,然后再来研究问题所在。请参考附录C提供的本书附带实例的安装指南。从光盘目录使用如下命
20、令运行该实例:nrtoki examples$ ant -Dchap=chap2 -Dex=0c run-exampleBuildfile: build.xml. java ERROR,UCL1 Failed to invoke ExCtx.useValue java java.lang.reflect.InvocationTargetException java at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) java at sun.reflect.NativeMethodAccessorImpl.invoke(N
21、ativeMethodAccessorImpl.java:39) java at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl. java:25) java at java.lang.reflect.Method.invoke(Method.java:324) java at org.jboss.chap2.ex0.ExCCEc.main(ExCCEc.java:58) java Caused by: java.lang.ClassCastException java at org.jb
22、oss.chap2.ex0.ExCtx.useValue(ExCtx.java:44) java . 5 more java Java Result: 1上述给出的只是异常信息,通过logs/chap2-ex0c.log文件能够找到完整的输出信息。其中,ExCCEc.java代码使用URLClassLoader ucl1装载了ExCtx类,然后实例化该类(第3748行)。最后,调用了ExCtx.useValue(Object) 方法(第55行)。另外,传进来的ExObj实例(译者注:value)是借助于URLClassLoader ucl0(第2535行)装载的。当ExCtx.useValue
23、代码试图将传入参数造型作为ExObj对象时,应用抛出了异常。为更好地理解失败的原因,列表2-4给出了摘自chap2-ex0c.log文件的调试输出信息。列表2-4 用于ExObj类的chap2-ex0c.log调试输出INFO,UCL0 ExObj Infoorg.jboss.chap2.ex0.ExObj(113fe2).ClassLoader=.URLClassLoader6e3914.URLClassLoader6e3914.file:/C:/Scott/JBoss/Books/AdminDevel/education/books/admin-devel/examples/output/
24、chap2/j0.jar+CodeSource: (file:/C:/Scott/JBoss/Books/AdminDevel/education/books/admin-devel/examples/output/chap2/j0.jar )Implemented Interfaces:+interface java.io.Serializable(7934ad)+ClassLoader: null+Null CodeSourceINFO,ExCtx useValue2.ExObj classorg.jboss.chap2.ex0.ExObj(415de6).ClassLoader=.URL
25、ClassLoader30e280.URLClassLoader30e280.file:/C:/Scott/JBoss/Books/AdminDevel/education/books/admin-devel/examples/output/chap2/j0.jar+CodeSource: (file:/C:/Scott/JBoss/Books/AdminDevel/education/books/admin-devel/examples/output/chap2/j0.jar )Implemented Interfaces:+interface java.io.Serializable(79
26、34ad)+ClassLoader: null+Null CodeSource带有前缀INFO,UCL0的第一条输出表明,ExCCEc.java中第31行装载的ExObj类的Hash码为113fe2,与之关联的URLClassLoader实例(ucl0)的Hash码为6e3941。传递给ExCtx.useValue方法的实例就是基于该ExOjb类创建的。带有前缀INFO,ExCtx的第二条输出表明,ExCtx.userValue方法上下文中的ExObj实例的Hash码为415de6,与之关联的URLClassLoader实例(ucl1)的Hash码为30e280。尽管这两个ExObj类是来自相
27、同j0.jar中的同一字节码文件,但结果是两者的散列码和URLClassLoader都不同。因此,试图将不同范围的ExObj实例进行造型转换,将会抛出ClassCastException异常。若开发者重新部署某应用,与此同时有其他的应用引用了该应用的类,则此时ClassCastException异常经常出现,如单独的Web应用(.war)访问EJB。如果重新部署了某应用,所有依赖于它的应用必须刷新(flush)它们的类引用。一般情况下,这要求依赖的应用程序能够自动重新部署。当出现了重新部署的情况时,为实现独立部署应用之间的交互,一种替代方式是通过配置EJB层以隔离不同的部署应用。其具体办法是,
28、将EJB层默认的引用访问语义修改为传值访问语义。因为基于引用访问语义时,JBoss设定部署在其上的组件默认运行在同一JVM中。2IllegalAccessException做不应该做的当试图访问可见性限定符(visibility qualifier)不允许访问的方法或成员时,应用会抛出java.lang.IllegalAccessException异常。常见的例子有:试图访问私有或受保护的方法和实例变量。再比如,存在某例子,从表面上看是从正确的包访问私有受保护的方法或成员,但实际上访问者和被访问者是由不同类装载器装载的。列表2-5给出了代码实例。列表2-5 由于不同类装载器引起IllegalA
29、ccessException的ExIAEd类116 package org.jboss.chap2.ex0;117118 import java.io.File;119 import .URL;120 import .URLClassLoader;121 import java.lang.reflect.Method;122123 import org.apache.log4j.Logger;124125 import org.jboss.util.ChapterExRepository;126 import org.jboss.util.Debug;127128 /* An example
30、of IllegalAccessExceptions due to classes loaded by two class129 * loaders.130 * author Scott.Starkjboss.org131 * version $Revision: 1.2$132 */133 public class ExIAEd134 135 public static void main(String args) throws Exception136 137 ChapterExRepository.init(ExIAEd.class);138139 String chapDir = Sy
31、stem.getProperty(chapter.dir);140 Logger ucl0Log = Logger.getLogger(UCL0);141 File jar0 = new File(chapDir+/j0.jar);142 ucl0Log.info(jar0 path: +jar0.toString();143 URL cp0 = jar0.toURL();144 URLClassLoader ucl0 = new URLClassLoader(cp0);145 Thread.currentThread().setContextClassLoader(ucl0);146147
32、StringBuffer buffer = new StringBuffer(ExIAEd Info);148 Debug.displayClassInfo(ExIAEd.class, buffer, false);149 ucl0Log.info(buffer.toString();150151 Class ctxClass1 = ucl0.loadClass(org.jboss.chap2.ex0.ExCtx);152 buffer.setLength(0);153 buffer.append(ExCtx Info);154 Debug.displayClassInfo(ctxClass1
33、, buffer, false);155 ucl0Log.info(buffer.toString();156 Object ctx0 = ctxClass1.newInstance();157158 try159 160 Class types = Object.class;161 Method useValue = ctxClass1.getDeclaredMethod(pkgUseValue, types);162 Object margs = null;163 useValue.invoke(ctx0, margs);164 165 catch(Exception e)166 167
34、ucl0Log.error(Failed to invoke ExCtx.pkgUseValue, e);168 169 170 应用类装载器将ExIAEd类装载到应用中,但ExCtx类是ExIAEd.main方法通过反射并借助于URLClassLoader ucl0装载的。这里将运行上述实例来证明如何抛出Illegal AccessException异常,并找到问题的原因。通过如下命令运行实例:orbtoki examples$ ant -Dchap=chap2 -Dex=0d run-exampleBuildfile: build.xml. java ERROR,UCL0 Failed t
35、o invoke ExCtx.pkgUseValue java java.lang.IllegalAccessException: Class org.jboss.chap2.ex0.ExIAEd can not access a member of class org.jboss.chap2.ex0.ExCtx with modifiers java at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:57) java at java.lang.reflect.Method.invoke(Method.java:317)
36、java at org.jboss.chap2.ex0.ExIAEd.main(ExIAEd.java:48)上述那条大块输出信息展示了IllegalAccessException异常的抛出。完整的输出信息能在logs/chap2-ex0d.log文件中找到。ExIAEd.java中的第48行,程序借助于反射调用了ExCtx.pkgUserValue(Object)方法。其中,pkgUseValue方法具有包受保护访问限定,尽管调用类ExIAEd和方法被调用的ExCtx类都位于包org.jboss.chap2.ex0中,但由于这两个类被不同类装载器装载,因此调用无效。通过查看chap2-ex0
37、d.log文件,能够找到重要的几行输出信息:INFO,UCL0 ExIAEd Infoorg.jboss.chap2.ex0.ExIAEd(65855a).ClassLoader=sun.misc.Launcher$AppClassLoader3f52a5.sun.misc.Launcher$AppClassLoader3f52a5.INFO,UCL0 ExCtx Infoorg.jboss.chap2.ex0.ExCtx(70eed6).ClassLoader=.URLClassLoader113fe2.URLClassLoader113fe2.通过上述内容可以看出,ExIAEd类是由默认的
38、应用类装载器实例,即sun.misc. Launcher$AppClassLoader3f52a2装载的,而ExCtx类是通过类装载器实例.URLClassLoader113FE2装载的。由于不同的类装载器装载了这些类,访问包受保护方法便成了安全侵害。因此,类的全限定名和类装载器不仅能够决定类的类型,包范围(package scope)也是如此。比如,在实际工作中,如果将相同的类放置在两个不同的SAR(译者注:JBoss Service Archive,JBoss服务存档)部署文件中就会出现这种异常。如果SAR部署文件中的类有包受保护关系,则使用SAR服务的用户可能从某个SAR装载某类,而后又
39、去另一个SAR文件中装载另一个类。如果这两个类有受保护关系,则会抛出IllegalAccessError异常。解决的办法可以是:开发者需要将SAR引用的类单独放在某个jar中,或者将这些SAR合并成单个部署文件。因此,有可能是单个的SAR,或者将这两个SAR包含在EAR(译者注:Enterprise Application Archive,企业应用存档)中。3LinkageError确保你就是声称的你为解决早期Java虚拟机中的类型安全问题,Java语言规范第1.2版引入了装载约束。当某X类涉及到多个类装载器时,为保证它的一致性,通过装载约束能够在类装载器范围的上下文内验证这种类型是否是预期的
40、。由于Java允许用户自定义类装载器,同时LinkageError还扩展了ClassCastException类,而且装载和使用类的过程中都需要完成装载约束,因此装载约束是很重要的。为了能够理解装载约束的内容和它是如何保证类型安全的,下面首先介绍Liang和Bracha论文中的术语和例子。当前存在两种类装载器,即初始和定义类装载器。其中,初始类装载器是通过调用ClassLoader.loadClass方法以初始装载命名类的类装载器。而定义类装载器是通过调用ClassLoader.defineClass方法将类字节码转换成类实例的类装载器。类的完整表达式为:其中,为全限定类名,Ld为定义类装载器
41、,Li为初始类装载器。当初始类装载器不重要时,类型可以表示为;当定义类装载器不重要时,类型可以表示为。对于第二种情形,并不是不存在定义类装载器,而只是标识它并不重要而已。同时,完全定义了类型。仅仅在验证装载约束时,初始装载器才会对产生关联。接下来,请开发者浏览列表2-6。列表2-6 用于证明装载约束的类class void f() x = g(); x.secret_value = 1 ; / Should not be allowed-class static g() -class public int secret_value;-class private int secret_value;其中:类由定义,因此用于初始装载类及引用的类。由定义,由定义(由委派给)。既然由定义,则将负责方法上下文中类的初始装入。本实例中的和定义了不同版本的(列表2-6后续内容指出了)。另外,由于1.1及先前版本的JVM并没有将类的全限定名和定义类装载器,这两者一同决定类的类型,因此既然认定x是实例,所以x能够访问返回中的私有成员secret_value。从Java 1.2以后,对于从不同定义类装载器中使用类型的情形