先来一个简单的例子,展示下问题:

系统中的几个角色类:

IFac - 接口,提供一个getMyData方法。

MyData - 数据类,没有实际意义,但是在系统中,这个类的加载则是引发问题的关键

IFacImpl - IFac接口的实现。

系统中还有一个不可或缺的角色——那个不遵守规则的ClassLoader。我们需要自己写一个ClassLoader来违法Parent Delegate的规则。

 1 package webcl;
2
3 import java.net.URL;
4 import java.net.URLClassLoader;
5 import java.net.URLStreamHandlerFactory;
6
7 public class WebAppCL extends URLClassLoader {
8
9 public WebAppCL(URL[] urls, ClassLoader parent,
10 URLStreamHandlerFactory factory) {
11 super(urls, parent, factory);
12 }
13
14 public WebAppCL(URL[] urls, ClassLoader parent) {
15 super(urls, parent);
16 }
17
18 public WebAppCL(URL[] urls) {
19 super(urls);
20 }
21
22 @Override
23 protected synchronized Class<?> loadClass(String name, boolean resolve)
24 throws ClassNotFoundException {
25 try {
26 return findClass(name);
27 } catch (ClassNotFoundException ex) {
28 return super.loadClass(name, resolve);
29 }
30 }
31
32 }

  

这个ClassLoader唯一的作用就是违反PD的规则。

然后是App程序:

 1 package test;
2
3 import java.net.URL;
4
5 import lib.IFac;
6 import lib.MyData;
7 import webcl.WebAppCL;
8 public class App2 { // 应用程序的classpath上有IFac类和MyData类。
9 public static void main(String[] args) throws Exception {
10 MyData resolved = new MyData();
11 WebAppCL cl = new WebAppCL(new URL[] {
12 new URL("file:\\C:\\Users\\zangmeng\\Desktop\\data.jar"), // 包含MyData类
13 new URL("file:\\C:\\Users\\zangmeng\\Desktop\\faclib.jar") }); // 包含FacImpl类,不包含IFac类。
14 IFac fac = (IFac) cl.loadClass("faclib.FacImpl").newInstance();
15 MyData data = fac.getData();
16 }
17 }

  

程序在运行时,类加载情况如下所示:

程序很简单,四行代码而已。

第一行是创建一个MyData的实例。目的是让MyData类被加载和解析。

第二行是创建一个WebAppCL,这个不听话的CL会优先加载自己classpath下的类,如果失败了再去问parent cl要。现在这个CL可以加载MyData类和FacImpl类。

第三行,首先是IFac fac这段。JVM需要使用当前类加载器,也就是AppClassLoader,加载并解析IFac类,解析的过程中,同时链接到AppCL加载的MyData类。等号的另一边,通过WebCl加载并创建一个FacImpl的实例。这个时候,因为WebAppCL会首先从自己的CP里加载类,所以在解析FacImpl的时候,加载MyData的请求并没有被委派到AppCL,而是自己自己消化了,这时候JVM里面就有两个MyData类了。但是程序到这里并没有错误,因为WebAppCL里面并没有IFac接口,这个接口还是AppCL的,但是这时候隐患已经埋下了——

FacImpl类在解析的时候,需要MyData类,而这个MyData类是被WebCL加载的。

IFac则不同,它的MyData类是被AppCL加载的。

FacImpl实现了IFac接口,按说getMyData方法应该返回相同的类型。而在runti的时候,这两个MyData却是不同的类型,因为它们是被不同的CL加载的。

第四行是引起错误的地方。等号左边的MyData实际上是第一行中那个被AppCL加载的类的一个引用。等号右边返回的MyData实例是被WebCL加载的MyData的实例。两个不同的类型,赋值自然会引发错误。

再回归正文:

LinkageError本身则更少见。当遇到LinkageError loader (instance of xxx) previously initiated loading for a different type with name "lib/MyData"时,可以肯定系统中有ClassLoader违背了Parent Delegate的规则。

问题的根源就是,当一个类已经被解析过之后,它用到的其它类也就已经确定并被解析好了。这时候,如果另一个CL也加载了同一个类(类名相同),并尝试将这个类的实例给前面的引用赋值,因为实际上两个类是由不同的类加载器加载的,既在JVM看来是不同的类,所以就会出现这个错误。

 

作者: 深夜两点

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"