对LinkageError的分析

同事开发时遇到些类加载的问题,在协助他fix的时候,自己也对类加载相关知识有了更深的理解。现在回顾总结下。

环境:运行在OSGI环境中

代码如下(需注意:我们自定义的ReviceContractPayInfo_Service 继承了rt.jar中javax.xml.ws.Service这个类):
import java.net.MalformedURLException;
import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.ws.WebEndpoint;
import javax.xml.ws.WebServiceClient;
import javax.xml.ws.WebServiceFeature;
import javax.xml.ws.Service;


@WebServiceClient(name = "reviceContractPayInfo", 
                  wsdlLocation = "http://10.0.123.123:8080/services/reviceContractPayInfo?wsdl",
                  targetNamespace = "http://webService.law.com") 
public class ReviceContractPayInfo_Service extends Service {

    public final static URL WSDL_LOCATION;
    public final static QName SERVICE = new QName("http://webService.law.com", "reviceContractPayInfo");
    static {
        URL url = null;
        try {
            url = new URL("http://10.0.123.123:8080/services/reviceContractPayInfo?wsdl");
        } catch (MalformedURLException e) {
        }
        WSDL_LOCATION = url;
    }
    public ReviceContractPayInfo_Service(URL wsdlLocation) {
        super(wsdlLocation, SERVICE);
    }

    public ReviceContractPayInfo_Service(URL wsdlLocation, QName serviceName) {
        super(wsdlLocation, serviceName);
    }

    public ReviceContractPayInfo_Service() {
        super(WSDL_LOCATION, SERVICE);
    }

}

rt.jar中Service的部分源码如下:

public class Service {
    protected Service(java.net.URL wsdlDocumentLocation, QName serviceName) {
        delegate = Provider.provider().createServiceDelegate(wsdlDocumentLocation,
                serviceName,
                this.getClass());
    }
}


抛出的异常信息如下:

java.lang.LinkageError: loader constraint violation: when resolving method "javax.xml.ws.Service.<init>(Ljava/net/URL;Ljavax/xml/namespace/QName;)V" the class loader (instance of org/eclipse/osgi/internal/baseadaptor/DefaultClassLoader) of the current class, com/qimiguang/fo/sdic/extend/intf/laywer/client/ReviceContractPayInfo_Service, and the class loader (instance of <bootloader>) for resolved class, javax/xml/ws/Service, have different Class objects for the type javax/xml/namespace/QName used in the signature
at com.qimiguang.fo.sdic.extend.intf.laywer.client.ReviceContractPayInfo_Service.<init>(ReviceContractPayInfo_Service.java:52)
at com.qimiguang.fo.sdic.extend.intf.Test.getcon(Test.java:18)
at com.qimiguang.fo.sdic.extend.intf.laywer.runner.FoLaywerSystemRunner.excute(FoLaywerSystemRunner.java:37)
at com.qimiguang.dna.bap.plantask.service.PlanTaskRecordService$RunnerExecuteTaskHandler.handle(PlanTaskRecordService.java:314)
at com.qimiguang.dna.bap.plantask.service.PlanTaskRecordService$RunnerExecuteTaskHandler.handle(PlanTaskRecordService.java:1)
at com.qimiguang.dna.core.impl.ServiceBase$TaskMethodHandler.handle(ServiceBase.java:1)
at com.qimiguang.dna.core.impl.ContextImpl.serviceHandleTask(ContextImpl.java:261)
at com.qimiguang.dna.core.impl.AsyncTaskImpl.workDoing(AsyncTaskImpl.java:146)
at com.qimiguang.dna.core.spi.work.Work.doWork(Work.java:345)
at com.qimiguang.dna.core.spi.work.WorkingThread.run(WorkingThread.java:44)


接下来先来介绍下LinkageError。java API对其这样定义:Subclasses of LinkageError indicate that a class has some dependency on another class; however, the latter class has incompatibly changed after the compilation of the former class.

大概是编译时,A类用到B类,且这时候A和B是兼容的,但是到运行时,A类得到的B(既某个类加载器加载的B类)却不是A在编译的时候那个与之兼容的B。LinkageError的继承结构如下(注意这里的NoClassDefFoundError):


先贴一段网友的总结

任何一个ClassLoader的实例都应该设置一个恰当的ClassLoader实例为父类加载器,并且在真正defineClass前,都应该先将这个任务委托给其父类加载器何为恰当的ClassLoader实例?一般来说,就应该是 this.getClass().getClassLoader()(也就是ClassName.class.getClassLoader(),这两个值是同一个值)的返回值),或者是一个设计者认为合理的值。这个值应该保证“相同全限定名的类(可以认为是同一个类,仅仅是可以)不会出现在同一个类的代码中。”举例来说,在一个类的一段代码中(可以是一个方法,或者是一个static块),如果有classLoaderA加载的 test.Test类,也有classLoaderB加载的test.Test,那么,把前者的引用指向后者的实例的时候,就会报LinkageError。


接下来分析console抛出的异常,异常信息翻译过来是:

当解析javax.xml.ws.Service的初始化方法时,加载ReviceContractPayInfo_Service类的类加载器(OSGI的DefaultClassLoader),与加载javax/xml/ws/Service类的类加载器(jdk的bootloader),对于javax/xml/namespace/QName有不同的class object。

原因很明确了,rt.jar中含有javax/xml/namespace/QName,ReviceContractPayInfo_Service所在的bundle中依赖了第三方的某个jar包( jaxrpc.jar),该包中也包含相同类路径名的javax/xml/namespace/QName。Original classloader from the JVM 会加载rt.jar下的Service和QName,而OSGI的DefaultClassLoader在加载ReviceContractPayInfo_Service时,加载到 jaxrpc.jar中的javax/xml/namespace/QName,然后在ReviceContractPayInfo_Service的构造函数中,调用super(*,QName)时,将不同类加载器加载的不同的QName的实例传给父类,导致抛的异常。


贴两篇相关的posts:

https://community.oracle.com/thread/2365207

http://stackoverflow.com/questions/244482/how-to-deal-with-linkageerrors-in-java

tip1:  The error you get seems to indicate that two copies of exactly the same class have been loaded, by two different classloaders. That is a conflicting problem as even though they are the same class, when loaded by different classloaders they are still treated as different classes by JVM. When a class loaded by classloader A gets an instance of the object created from the conflicting class as loaded by Classloader B, you are in classpath hell.
Long story short is that apparently you have the same QName class on the classpath twice; one coming from the JDK and one coming from somewhere else. Are you deploying any XML based libraries with your application, such as JAXB or JAX-WS? Are there perhaps any libraries in the lib/ext folder that shouldn't be there?

tip2:LinkageError is what you'll get in a classic case where you have a class C loaded by more than one classloader and those classes are being used together in the same code(compared, cast, etc). It doesn't matter if it is the same Class name or even if it's loaded from the identical jar - a Class from one classloader is always treated as a different Class if loaded from another classloader.


版权声明:本文为qimiguang原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。