类加载器的相关问题(ClassLoader&&LinkageError)

下面的错误在google上搜了半天也没有特别满意的答案,后来看到一篇关于JUnit类加载器Bug的文章,总算觉得柳暗花明....
  

java.lang.LinkageError: Class org/w3c/dom/Document violates loader constraints at
java.lang.ClassLoader.defineClass0(Native Method) at
java.lang.ClassLoader.defineClass(ClassLoader.java:502) at
java.lang.ClassLoader.defineClass(ClassLoader.java:431) at
.......

这样的异常,主要是由于非系统 类加载器,即自定义的类加载器出现了问题,这个类加载器试图加载org.w3c.dom.Document ,此时如果类加载器没有进行正确的处理,或者Document 类不在JVM's system classpath下,类加载器便无法加载,自己加载不了,也不通知系统类加载器,上面的错误就出现啦。。。

可能理解有问题,可参照下面这篇文章,我试验了一下,完全可信:


Here's a patch for JUnit's busted classloader: junit-patch.jar.  And below is XmlTest.java, a regression test that breaks (with a java.lang.LinkageError) with the old classloader and the swingui/awtui TestRunners, but passes with the patch.

junit-patch.jar contains two classes (source included):

  1. ReloadableTestClassLoader - a properly delegating custom ClassLoader that currently relies on a System property value for its classpath: junit.test.path. The fix can be ported to junit.runner.TestCaseClassLoader or it can be effected through a new ClassLoader like this one.
  2. ReloadingTestSuiteLoader - modified to return this custom ClassLoader. If I can get this fix applied to the real JUnit, this class wouldn't necessarily need to be modified (it's only like 5 lines) because we could fix the original TestCaseClassLoader instead of writing a new one. Whatever; minor detail.

XmlTest.java:

import junit.framework.TestCase;
import javax.xml.parsers.*;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
    
public class XmlTest extends TestCase {

Document doc = null; public void testXml() throws ParserConfigurationException { doc = DocumentBuilderFactory.newInstance() .newDocumentBuilder() .newDocument(); // System.out.println("Hello! I changed again"); Element root = doc.createElement("xml-test"); assertNotNull(root); } }

This is a simple test that reproduces a LinkageError with the JUnit buggy ClassLoader under the following conditions:

  1. Use Sun JDK 1.4.x
  2. Edit JUnit's excluded.properties so that you comment out the exclusion of org.w3c.dom.* -- this is necessary to reveal the bug and is the easiest test I could think of. Make sure your edited excluded.properties is first in the classpath or updated into junit.jar at junit/runner/excluded.properties (BTW, obviously the new ReloadableTestClassLoader doesn't use or rely on excluded.properties at all).
  3. Put the DOM interfaces (in Xerces these are in xml-apis.jar) and an XML parser impl, such as Xerces, in your classpath to reproduce the bug. Why? Since you will have unexcluded org.w3c.dom.* in excluded.properties, JUnit will attempt to load those interfaces rather than delegate to the system loader. But they aren't in the scope of JUnit's buggy classloader if they aren't in the JVM's system classpath, so you have to add them. I.e., the 1.4.x JVM loads these in its boot loader, whose classpath JUnit's buggy loader doesn't know about.
  4. Example of command line to use to reproduce the bug (using junit.jar from JUnit 3.8.1, but any version should work): java -cp .;junit.jar;xml-apis.jar;xerces.jar junit.swingui.TestRunner XmlTest

You should see this in the runner's error display window:

 java.lang.LinkageError: Class org/w3c/dom/Document violates loader constraints at
java.lang.ClassLoader.defineClass0(Native Method) at 
java.lang.ClassLoader.defineClass(ClassLoader.java:502) at 
java.lang.ClassLoader.defineClass(ClassLoader.java:431) at 
junit.runner.TestCaseClassLoader.loadClass(TestCaseClassLoader.java:104) at 
java.lang.ClassLoader.loadClass(ClassLoader.java:255) at 
java.lang.ClassLoader.loadClassInternal(ClassLoader.java:315) at 
XmlTest.testXml(XmlTest.java:16) at 
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at 
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at 
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at 
java.lang.reflect.Method.invoke(Method.java:324) at 
junit.framework.TestCase.runTest(TestCase.java:154) at 
junit.framework.TestCase.runBare(TestCase.java:127) at 
junit.framework.TestResult$1.protect(TestResult.java:106) at 
junit.framework.TestResult.runProtected(TestResult.java:124) at 
junit.framework.TestResult.run(TestResult.java:109) at 
junit.framework.TestCase.run(TestCase.java:118) at 
junit.framework.TestSuite.runTest(TestSuite.java:208) at 
junit.framework.TestSuite.run(TestSuite.java:203) at 
junit.swingui.TestRunner$16.run(TestRunner.java:623)

Now run the same test with the fixed ClassLoader:

  1. shadow classes in junit.jar by listing junit-patch.jar first in the classpath
  2. set junit.test.path property to include the path to XmlTest.class
  3. run XmlTest with the Swing or AWT test runner:
    java -cp junit-patch.jar;junit.jar -Djunit.test.path=. junit.swingui.TestRunner XmlTest
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章