IlegeAccessException和Weblogic类加载器

背景 

项目上使用的Weblogic版本为10.3,我们用JAWR来减少压缩JS和CSS,JAWR默认使用JSMin来压缩JS,我尝试修改JAWR的JS压缩器为压缩率更高的YUI Compressor,YUI依赖javascript解析器js.jar,由于Weblogic自身已经包含了js.jar中的class,所以我们将js.jar放在weblogic的domain lib下,并修改了weblogic的启动脚本使得启动的时候先加载项目用到的js.jar。

问题 

修改JAWR的JS压缩器为YUI后,在Tomcat下测试通过,但在Weblogic下报如下的错误:

1 IlegeAccessException
2 Caused By: java.lang.IllegalAccessError: tried to access class org.mozilla.javascript.DefaultErrorReporter from class org.mozilla.javascript.CompilerEnvirons
3 at org.mozilla.javascript.CompilerEnvirons.<init>(CompilerEnvirons.java:48)
4 at com.yahoo.platform.yui.compressor.JavaScriptCompressor.parse(JavaScriptCompressor.java:310)
5 at com.yahoo.platform.yui.compressor.JavaScriptCompressor.<init>(JavaScriptCompressor.java:533)
6 at net.jawr.web.resource.bundle.postprocess.impl.yui.YUIJSCompressor.doPostProcessBundle(YUIJSCompressor.java:66)
7 at net.jawr.web.resource.bundle.postprocess.AbstractChainedResourceBundlePostProcessor.postProcessBundle(AbstractChainedResourceBundlePostProcessor.java:71)
8 Truncated. see log file for complete stacktrace

初步怀疑是jar包冲突。可能是因为错误里涉及的2个类是由不同的类加载器加载引起的。

Java类加载器

解决问题前,先了解下Java类加载器的原理。

我们知道java中所有类都是通过类加载器ClassLoader来加载的,Java提供了四种类加载器:

1. BootStrap:在java虚拟机启动的时候会利用这个类加载器来加载System.getProperty("sun.boot.class.path")所指定的路径或jar,一般为JDK安装目录下的 /JRE/LIB/rt.jar,这个类加载器不是类 ,只是作为一个java中类的起源工具。BootStrap是用C++语言写的。

2. ExpClassLoader:这个类加载器加载System.getProperty("java.ext.dirs")所指定的路径或jar,一般为JDK安装目录下的/JRE/LIB/ext 目录中的类 我们只要把我们的类打包成JAR包放在这里即可。

3. AppClassLoader:这个类加载器加载System.getProperty("java.class.path")所指定的路径或jar。我们在java程序中classpath对应的类都由这个AppClassLoader导入进来。

4. URLClassLoader用来加载网络上远程的类。

这4种ClassLoader的优先级依次从高到低。在java中,一个类通过认证的类全名来唯一标识。认证的类全名是由包名和类名两部分组成。但是在一个类被加载到JVM中则是通过认证的类全名,还有加载这个类的加载器来唯一标识。因此,一个类的类名为C1,包名为Pg,被类加载器类KClassLoader的一个实例k1加载,则C1,也就是C1.class ,的类实例,在JVM中将被解释为(C1,Pg,k1)。这就意味着两个不同的类加载器实(Cl, Pg, kl1) 和 (Cl, Pg, kl2) ,加载的类在JVM中将有不同的类实例对象,不是类型可比型(type-compatible)的。

双亲委派模型

如果一个网络类装载器被请求装载一个java.lang.Integer,它会首先把请求发送给上一级的类路径装载器,如果返回已装载,则网络类装载器将不会装载这个java.lang.Integer,如果上一级的类路径装载器返回未装载,它才会装载java.lang.Integer。类似地,类路径装载器收到请求后(无论是直接请求装载还是下一级的ClassLoader上传的请求),它也会先把请求发送到上一级的标准扩展类装载器,这样一层一层上传,于是启动类装载器优先级最高,如果它按照自己的方式找到了java.lang.Integer,则下面的ClassLoader 都不能再装载java.lang.Integer,尽管你自己写了一个java.lang.Integer,试图取代核心库的java.lang.Integer是不可能的,因为自己写的这个类根本无法被下层的ClassLoader装载。 

Weblogic类加载器

Weblogic默认情况下采用的是parent first的方式。但这个parent first,是有“讲究”(tricky)的。 

1. 父类加载器和子类加载器之间的关系类似于Java中,父类和子类之间的对象关系。 

2. Weblogic会将所有load到的class缓存到cache中。(子类classloader能看到父类classloader加载到cache中的class)。 

默认情况下,当我们的应用程序(ear,war)运行时,会先去cache中查找class,如果找不到。就去System Classpath loader 里去找class。如果System Classpath loader里能找到你需要的类,那么不好意思,你在ear和war包里包含的class就没用了。如果System ClassPath Loader找不到,接下来去ear的class path里找,接着去EJB class path里找,最后到war的class path里找。一旦找到了该类,就会load起这个类,并将该类放入cache中。 

 

总的来说,Weblogic的ClassLoader有如下几个层次,按照从高到低顺序排列:

1. JDK Classloader

2. JDK ext Class Loader

3. Weblogic System Class Loader

4. Domain Class Loader(Child of System Class Loader)

5. App Class Loader

6. Web Class Loader

7. JSP Class Loader

可以从Weblogic官方文档获取更多的关于Weblogic ClassLoader的信息:http://docs.oracle.com/cd/E23943_01/web.1111/e13706/classloading.htm#autoId32

Root Cause

Java语言规定,在同一个包中的class,如果没有修饰符,默认为Package权限,包内的class都可以访问。但是这还不够准确。确切的说,只有由同一个ClassLoader装载的class才具有以上的Package权限。比如启动类装载器装载了java.lang.String,类路径装载器装载了我们自己写的java.lang.Test,它们不能互相访问对方具有Package权限的方法。这样就阻止了恶意代码访问核心类的Package权限方法。

在上面的问题中,当应用启动时,WAR包的加载器会加载类CompilerEnvirons,这个类有一个static的属性引用了DefaultErrorReporter,然后到Cache中查找该类是否加载,可能Weblogic在启动的时候已经用到了类DefaultErrorReporter(待验证,可以通过getClassLoader来获取加载器类,也可以通过weblogic提供的CAT工具来分析), 也就是说该类已经存在Cache中了,所以导致CompilerEnvirons和DefaultErrorReporter由不同的加载器加载,CompilerEnvirons不能访问只有包访问级别的DefaultErrorReporter(该类没有加public修饰符)。

解决办法

修改weblogic的启动脚本startWebLogic.sh,将YUI Compressor jar放在所有jar包的前面加载,修改如下:CLASSPATH= "js.jar: ${SAVE_CLASSPATH}"。

另外,weblogic提供了一个标签<prefer-web-inf-classes> ,这个标签默认是false的,只要设置这个标签为true,就可以让WEB-INF里的类先被load到了。但这个并不能解决上面的问题,因为类DefaultErrorReporter已经被更高级别的加载器加载了,当应用需要使用该Class时,只会从Cache中获取。

参考

Weblogic与Java类加载器原理试验解析

Weblogic10 Classloading 问题

java中的类加载机制(一)

java中的类加载机制(二) 

深入研究java类加载机制

Weblogic 官方文档

posted @ 2012-05-01 22:07  先行而后三思  阅读(1029)  评论(0编辑  收藏  举报