本文将通过多个例子来说明 LinkageError 出现的场景。其中有些例子很有趣,甚至会出乎你的意料。
jetty 部署应用的时候,出现了Java LinkageError:
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.eclipse.jetty.webapp.IterativeDescriptorProcessor.visit(IterativeDescriptorProcessor.java:84)
...
Caused by:
java.lang.LinkageError: loader constraint violation: when resolving method "org.slf4j.impl.StaticLoggerBinder.getLoggerFactory()Lorg/slf4j/ILoggerFactory;" the class loader (instance of org/eclipse/jetty/webapp/WebAppClassLoader) of the current class, org/slf4j/LoggerFactory, and the class loader (instance of java/net/URLClassLoader) for the method's defining class, org/slf4j/impl/StaticLoggerBinder, have different Class objects for the type org/slf4j/ILoggerFactory used in the signature
at org.slf4j.LoggerFactory.getILoggerFactory(LoggerFactory.java:418)
at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:357)
at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:383)
at test.TestListener.(TestListener.java:10)
...
应用配置 web.xml
test.TestListener
TestListener的代码:
package test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class TestListener implements ServletContextListener {
private static final Logger logger = LoggerFactory.getLogger(TestListener.class);
public void contextInitialized(ServletContextEvent sce) {
logger.info("context initialized.");
}
public void contextDestroyed(ServletContextEvent sce) {
logger.info("context destroyed.");
}
}
maven的配置 pom.xml
org.slf4j
slf4j-api
1.7.22
javax.servlet
servlet-api
2.5
provided
jetty的目录结构
.
├── conf
│ ├── jetty-channel.xml
│ └── jetty.xml
├── jetty-runner.jar
├── lib
│ ├── logback-classic-1.0.13.jar
│ ├── logback-core-1.0.13.jar
│ └── slf4j-api-1.7.22.jar
├── startup (启动脚本)
├── tmp (war包解压的目录)
└── webapps
├── jetty-test-1.0.war
├── jetty-test-1.0.xml
jetty 的启动脚本:startup
java -jar jetty-runner.jar \
--lib lib \
--out server.out \
--port 18080 \
--stop-port 18081 \
--stop-key stop_jetty \
--config conf/jetty-channel.xml \
--config conf/jetty.xml \
webapps/jetty-test-1.0.xml
方案1:修改maven pom.xml,对 slf4j-api dependency 添加 provided的scope
org.slf4j
slf4j-api
1.7.22
provided
方案2:修改maven pom.xml,加上 slf4j 的实现依赖
org.slf4j
slf4j-api
1.7.22
ch.qos.logback
logback-classic
1.0.13
ch.qos.logback
logback-core
1.0.13
先好好解读一下异常信息:
LoggerFactory的源码:
public static ILoggerFactory getILoggerFactory() {
...
switch (INITIALIZATION_STATE) {
case SUCCESSFUL_INITIALIZATION:
return StaticLoggerBinder.getSingleton().getLoggerFactory(); // 在这里调用的
...
}
查看jetty源码,可以发现,jetty启动时候如果带了"–lib xxx"参数,就会使用URLClassLoader来加载对应的jar包
public void configure(String[] args) throws Exception
{
// handle classpath bits first so we can initialize the log mechanism.
for (int i=0;i 0)
{
ClassLoader context=Thread.currentThread().getContextClassLoader();
if (context==null)
_classLoader=new URLClassLoader(paths);
else
_classLoader=new URLClassLoader(paths, context);
Thread.currentThread().setContextClassLoader(_classLoader);
}
}
在例子1中,jetty 启动脚本是带了 "–lib"参数,lib目录含有的jar包:
├── lib
│ ├── logback-classic-1.0.13.jar
│ ├── logback-core-1.0.13.jar
│ └── slf4j-api-1.7.22.jar
jetty在加载web应用的时候,每个应用都使用一个独立的WebAppClassLoader,它继承了URLClassLoader,应用启动时,会把自己lib目录下的所有jar包和classes目录添加到它的资源列表中(URLClassPath),查看它的 loadClass()方法,可以知道它会优先从自己的资源列表中查找class:
@Override
protected Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
ClassNotFoundException ex = null;
Class> parent_class = null;
Class> webapp_class = null;
// 如果已经加载过,则直接返回
webapp_class = findLoadedClass(name);
if (webapp_class != null) {
if (LOG.isDebugEnabled())
LOG.debug("found webapp loaded {}", webapp_class);
return webapp_class;
}
// 看是否优先使用parent来查找
if (_context.isParentLoaderPriority()) {
// 一般都不会走这个分支,因为web应用应该优先使用自己的class和jar
...
} else {
// 从这里可以看出,应用会优先使用自己的资源来查找class
// loadAsResource方法会调用URLClassLoader的findResource来查找class的URL
webapp_class = loadAsResource(name, true);
if (webapp_class != null) {
return webapp_class;
}
// 往下是使用parent来加载
...
}
}
}
在例子1中,应用的lib目录下包含了slf4j-api.jar (在pom.xml中指定的依赖)
测试代码:
package test;
public class TestJetty {
public static void main(String[] ss) throws Exception {
ClassLoader testLoader = new ClassLoader(Thread.currentThread().getContextClassLoader()) {
@Override
protected Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (name.contains(".slf4j."))
System.out.println("==========Jetty loader load class: " + name);
return super.loadClass(name, resolve);
}
};
Thread.currentThread().setContextClassLoader(testLoader);
String dir = "/xxx/jetty-test/jetty-app/";
String[] args = new String[]{
"--lib", dir + "lib",
"--port", "8080",
"--stop-port", "8081",
"--stop-key", "stop_jetty-test",
"--config", dir + "conf/jetty-channel.xml",
"--config", dir + "conf/jetty.xml",
dir + "webapps/jetty-test-without-log.xml"
};
Class> runnerClass = testLoader.loadClass("org.eclipse.jetty.runner.Runner");
runnerClass.getDeclaredMethod("main", String[].class)
.invoke(null, (Object) args);
}
}
maven的pom.xml:
org.eclipse.jetty
jetty-runner
9.4.14.v20181114
应用的war包跟例子1相同。
另外修改一下WebAppClassLoader.loadClass()方法,加入一些打印信息,运行结果如下:
...
WebApp load class: test.TestListener
--> load by self
WebApp load class: org.slf4j.LoggerFactory
--> load by self
WebApp load class: org.slf4j.ILoggerFactory
--> load by self
...
WebApp load class: org.slf4j.impl.StaticLoggerBinder
--> delegate to parent...
==========Jetty load class: org.slf4j.impl.StaticLoggerBinder
==========Jetty load class: org.slf4j.spi.LoggerFactoryBinder
==========Jetty load class: org.slf4j.ILoggerFactory
==========Jetty load class: org.slf4j.Logger
==========Jetty load class: org.slf4j.spi.LocationAwareLogger
java.lang.LinkageError: loader constraint violation: when resolving method "org.slf4j.impl.StaticLoggerBinder.getLoggerFactory()Lorg/slf4j/ILoggerFactory;" the class loader (instance of org/eclipse/jetty/webapp/WebAppClassLoader) of the current class, org/slf4j/LoggerFactory, and the class loader (instance of java/net/URLClassLoader) for the method's defining class, org/slf4j/impl/StaticLoggerBinder, have different Class objects for the type org/slf4j/ILoggerFactory used in the signature
at org.slf4j.LoggerFactory.getILoggerFactory(LoggerFactory.java:418)
at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:357)
at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:383)
at test.TestListener.(TestListener.java:10)
...
接下来的几个例子,将会探讨 LinkageError 出现的场合
将会用到的公共代码:
AbstractClassLoader.java
package test;
public class AbstractClassLoader extends URLClassLoader {
private String name;
private String msg1;
private String msg2;
public AbstractClassLoader(String name, String msg1, String msg2, URL[] urls, ClassLoader parent) {
super(urls, parent);
this.name = name;
this.msg1 = msg1;
this.msg2 = msg2;
}
@Override
public String toString() {
return name;
}
@Override
protected Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {
try {
Class> clazz = findLoadedClass(name);
if (clazz == null)
clazz = findClass(name);
System.out.println(msg1 + ": " + name);
return clazz;
} catch (Exception e) {
System.out.println(msg2 + ": " + name);
return getParent().loadClass(name);
}
}
}
class ParentClassLoader extends AbstractClassLoader {
public ParentClassLoader(String msg1, String msg2, URL[] urls, ClassLoader parent) {
super("ParentLoader", msg1, msg2, urls, parent);
}
}
class ChildClassLoader extends AbstractClassLoader {
public ChildClassLoader(String msg1, String msg2, URL[] urls, ClassLoader parent) {
super("ChildLoader", msg1, msg2, urls, parent);
}
}
Intf.java
package test;
public class Impl implements Intf {
}
Impl.java
package test;
public class Impl implements Intf {
}
Caller.java
package test;
public class Caller {
public Intf test() {
return new Impl();
}
}
先把类copy到不同的目录
test-all-class目录:
test-all-class
└── test
├── Caller.class
├── Impl.class
└── Intf.class
test-class3目录:
test-class3
└── test
├── Caller.class
└── Intf.class
下面是测试代码:
package test;
import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
public class LinkageErrorTest {
public static void main(String[] args) throws Exception {
String dir = "/xxx/";
ClassLoader parentLoader = new ParentClassLoader(
"load by parent",
"load by system",
new URL[]{
new File(dir + "test-all-class").toURI().toURL()
}, Thread.currentThread().getContextClassLoader());
ClassLoader aLoader = new ChildClassLoader(
"load by self",
"delegate to parent",
new URL[]{
new File(dir + "test-class3").toURI().toURL(),
}, parentLoader);
System.out.println("---- load Caller class -----");
Class> clazz = aLoader.loadClass("test.Caller");
System.out.println("\n---- new Caller instance -----");
Object o = clazz.newInstance();
System.out.println("\n---- get Caller.test() method -----");
Method method = clazz.getMethod("test");
System.out.println("\n---- invoke Caller.test() method -----");
Object retVal = method.invoke(o);
System.out.println("\n---- self load Intf class -----");
Class> intfClazzFromSelf = aLoader.loadClass("test.Intf");
System.out.println("check method return type equals self->Intf: " + method.getReturnType().equals(intfClazzFromSelf));
System.out.println("\n---- parent load Intf class -----");
Class> intfClazzFromParent = parentLoader.loadClass("test.Intf");
System.out.println("check method return type equals parent->Intf: " + method.getReturnType().equals(intfClazzFromParent));
try {
intfClazzFromParent.cast(retVal);
System.out.println("\ncast return value by parent->Intf is ok.");
} catch (Exception e) {
System.out.println("\ncast return value by parent->Intf failed: " + e.getMessage());
}
try {
method.getReturnType().cast(retVal);
System.out.println("\ncast return value by method return type is ok.");
} catch (Exception e) {
System.out.println("\ncast return value by method return type failed: " + e.getMessage());
}
System.out.println("\nself->Intf signature: " + intfClazzFromSelf.getClassLoader() + "->" + intfClazzFromSelf.getName());
System.out.println("parent->Intf signature: " + intfClazzFromParent.getClassLoader() + "->" + intfClazzFromParent.getName());
}
}
输出的结果:
---- load Caller class -----
delegate to parent: java.lang.Object
load by system: java.lang.Object
load by self: test.Caller
---- new Caller instance -----
load by self: test.Intf
---- get Caller.test() method -----
---- invoke Caller.test() method -----
delegate to parent: test.Impl
load by system: java.lang.Object
load by parent: test.Intf
load by parent: test.Impl
---- self load Intf class -----
load by self: test.Intf
method return type equals self->Intf: true
---- parent load Intf class -----
load by parent: test.Intf
check method return type equals parent->Intf: false
cast return value by parent->Intf is ok.
cast return value by method return type failed: Cannot cast test.Impl to test.Intf
self->Intf signature: ChildLoader->test.Intf
parent->Intf signature: ParentLoader->test.Intf
是不是很神奇?竟然没有出现 LinkageError。
解释说明:
那么,问题来了:
新增一个类 Impl2,注意它跟 Intf 没有半毛钱关系
package test;
public class Impl2 {
}
添加到test-class5目录:
test-class5
└── test
├── Caller.class
├── Impl2.class
└── Intf.class
test-all-class目录的内容保持不变:
test-all-class/
└── test
├── Caller.class
├── Impl.class
└── Intf.class
接下来,我们修改 Caller的字节码,使得它的 test()方法返回 Impl2的实例:
public Intf test() {
return new Impl();
}
// 注意:修改后,返回类型不变,但是返回值改变,这是铁定compile不过的,
// 所以我们需要通过修改字节码的方式来完成
public Intf test() {
return new Impl2();
}
maven的pom.xml
org.ow2.asm
asm
6.2.1
org.ow2.asm
asm-util
6.2.1
org.ow2.asm
asm-analysis
6.2.1
org.ow2.asm
asm-commons
6.2.1
先使用asm打印一下生成 Caller字节码的代码:
package pratice.asm;
import org.objectweb.asm.util.ASMifier;
import test.Caller;
public class TestASMifier {
public static void main(String[] args) throws Exception {
ASMifier.main(new String[]{Caller.class.getName()});
}
}
输出结果:
package asm.test;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.ConstantDynamic;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.TypePath;
public class CallerDump implements Opcodes {
public static byte[] dump () throws Exception {
ClassWriter classWriter = new ClassWriter(0);
FieldVisitor fieldVisitor;
MethodVisitor methodVisitor;
AnnotationVisitor annotationVisitor0;
classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, "test/Caller", null, "java/lang/Object", null);
classWriter.visitSource("Caller.java", null);
{
methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "", "()V", null, null);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(3, label0);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false);
methodVisitor.visitInsn(RETURN);
Label label1 = new Label();
methodVisitor.visitLabel(label1);
methodVisitor.visitLocalVariable("this", "Ltest/Caller;", null, label0, label1, 0);
methodVisitor.visitMaxs(1, 1);
methodVisitor.visitEnd();
}
{
methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "test", "()Ltest/Intf;", null, null);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(5, label0);
methodVisitor.visitTypeInsn(NEW, "test/Impl");
methodVisitor.visitInsn(DUP);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "test/Impl", "", "()V", false);
methodVisitor.visitInsn(ARETURN);
Label label1 = new Label();
methodVisitor.visitLabel(label1);
methodVisitor.visitLocalVariable("this", "Ltest/Caller;", null, label0, label1, 0);
methodVisitor.visitMaxs(2, 1);
methodVisitor.visitEnd();
}
classWriter.visitEnd();
return classWriter.toByteArray();
}
}
其实你压根不用关心这些代码,照搬就可以。
但是这里我们需要修改一下,把 “test/Impl” 修改为 "test/Impl2"即可,修改后的代码如下:
package test;
import org.objectweb.asm.*;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
public class TestCreateImpl implements Opcodes {
public static void main(String[] args) throws Exception {
byte[] bs = dump();
String path = "/xxx/test-class5/test/Caller.class";
try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(path))) {
out.write(bs, 0, bs.length);
}
}
private static byte[] dump() throws Exception {
ClassWriter classWriter = new ClassWriter(0);
FieldVisitor fieldVisitor;
MethodVisitor methodVisitor;
AnnotationVisitor annotationVisitor0;
classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, "test/Caller", null, "java/lang/Object", null);
classWriter.visitSource("Caller.java", null);
{
methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "", "()V", null, null);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(3, label0);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false);
methodVisitor.visitInsn(RETURN);
Label label1 = new Label();
methodVisitor.visitLabel(label1);
methodVisitor.visitLocalVariable("this", "Ltest/Caller;", null, label0, label1, 0);
methodVisitor.visitMaxs(1, 1);
methodVisitor.visitEnd();
}
{
methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "test", "()Ltest/Intf;", null, null);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(5, label0);
methodVisitor.visitTypeInsn(NEW, "test/Impl2");
methodVisitor.visitInsn(DUP);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "test/Impl2", "", "()V", false);
methodVisitor.visitInsn(ARETURN);
Label label1 = new Label();
methodVisitor.visitLabel(label1);
methodVisitor.visitLocalVariable("this", "Ltest/Caller;", null, label0, label1, 0);
methodVisitor.visitMaxs(2, 1);
methodVisitor.visitEnd();
}
classWriter.visitEnd();
return classWriter.toByteArray();
}
}
运行后,test-class5目录下的 Caller.class 将会被替换。
测试代码跟例子3的一样,只是把 SelfLoader的目录从test-class3改成test-class5。
运行结果:
---- load Caller class -----
delegate to parent: java.lang.Object
load by system: java.lang.Object
load by self: test.Caller
---- new Caller instance -----
load by self: test.Intf
---- get Caller.test() method -----
---- invoke Caller.test() method -----
load by self: test.Impl2
---- self load Intf class -----
load by self: test.Intf
check method return type equals self->Intf: true
---- parent load Intf class -----
load by system: java.lang.Object
load by parent: test.Intf
check method return type equals parent->Intf: false
cast return value by parent->Intf failed: Cannot cast test.Impl2 to test.Intf
cast return value by method return type failed: Cannot cast test.Impl2 to test.Intf
self->Intf signature: ChildLoader->test.Intf
parent->Intf signature: ParentLoader->test.Intf
输出跟例子3差不多,只是因为我们替换了test()方法的返回值,所以使得2个类型转换都失败,但是代码本身没有报错,这也就说明了,在用反射调用方法时,返回值类型都被认为是Object,所以没有再去校验是否跟方法的返回类型相匹配。
在例子3的公共类基础上,加入:
Delegate.java
package test;
public class Delegate {
public Intf get() {
return new Impl();
}
public static Intf getImpl() {
return new Impl();
}
}
test-all-class目录的内容:
test-all-class/
└── test
├── Caller.class
├── Delegate.class
├── Impl.class
└── Intf.class
test-class6目录的内容:
test-class6
└── test
├── Intf.class
├── TargetCaller1.class
├── TargetCaller2.class
├── TargetCaller3.class
├── TargetCaller4.class
└── TargetCaller5.class
测试代码:
package test;
import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
public class TargetTest {
public static void main(String[] args) throws Exception {
String dir = "/xxx/";
ClassLoader parentLoader = new ParentClassLoader(
"load by parent",
"load by system",
new URL[]{
new File(dir + "test-all-class").toURI().toURL()
}, Thread.currentThread().getContextClassLoader());
ClassLoader aLoader = new ChildClassLoader(
"load by self",
"delegate to parent",
new URL[]{
new File(dir + "test-class6").toURI().toURL(),
}, parentLoader);
Class> clazz = aLoader.loadClass("test.TargetCaller1");
Object o = clazz.newInstance();
Method method = clazz.getMethod("test");
System.out.println("---------");
Object retVal = method.invoke(o);
System.out.println(retVal);
}
}
TargetCaller1.java
package test;
public class TargetCaller1 {
private static Intf v = Delegate.getImpl();
public Intf test() {
return v;
}
}
输出结果:
delegate to parent: java.lang.Object
load by system: java.lang.Object
load by self: test.TargetCaller1
delegate to parent: test.Delegate
load by system: java.lang.Object
load by parent: test.Delegate
load by parent: test.Intf
load by parent: test.Impl
Exception in thread "main" java.lang.LinkageError: loader constraint violation: loader (instance of test/ChildClassLoader) previously initiated loading for a different type with name "test/Intf"
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
at test.AbstractClassLoader.loadClass(AbstractClassLoader.java:28)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.getDeclaredMethods0(Native Method)
at java.lang.Class.privateGetDeclaredMethods(Class.java:2701)
at java.lang.Class.privateGetMethodRecursive(Class.java:3048)
at java.lang.Class.getMethod0(Class.java:3018)
at java.lang.Class.getMethod(Class.java:1784)
at test.TargetTest.main(TargetTest.java:25)
解释说明:
换成TargetCaller2进行测试
TargetCaller2.java
package test;
public class TargetCaller2 {
public Intf test() {
return Delegate.getImpl();
}
}
输出结果:
delegate to parent: java.lang.Object
load by system: java.lang.Object
load by self: test.TargetCaller2
load by self: test.Intf
---------
delegate to parent: test.Delegate
load by system: java.lang.Object
load by parent: test.Delegate
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at test.TargetTest.main(TargetTest.java:26)
Caused by: java.lang.LinkageError: loader constraint violation: loader (instance of test/ParentClassLoader) previously initiated loading for a different type with name "test/Intf"
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
at test.AbstractClassLoader.loadClass(AbstractClassLoader.java:28)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at test.TargetCaller2.test(TargetCaller2.java:6)
... 5 more
解释说明:
换成TargetCaller3进行测试
TargetCaller3.java
package test;
public class TargetCaller3 {
public Intf test() {
return new Delegate().get();
}
}
输出结果:
delegate to parent: java.lang.Object
load by system: java.lang.Object
load by self: test.TargetCaller3
load by self: test.Intf
---------
delegate to parent: test.Delegate
load by system: java.lang.Object
load by parent: test.Delegate
load by parent: test.Intf
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at test.TargetTest.main(TargetTest.java:26)
Caused by: java.lang.LinkageError: loader constraint violation: when resolving method "test.Delegate.getImpl()Ltest/Intf;" the class loader (instance of test/ChildClassLoader) of the current class, test/TargetCaller3, and the class loader (instance of test/ParentClassLoader) for the method's defining class, test/Delegate, have different Class objects for the type test/Intf used in the signature
at test.TargetCaller3.test(TargetCaller3.java:6)
... 5 more
原因可参考例子1和2
换成TargetCaller4进行测试
TargetCaller4.java
package test;
public class TargetCaller4 {
public Intf test() {
Intf v = new Impl();
System.out.println("v classLoader: " + v.getClass().getClassLoader());
return (Intf) v;
}
}
输出结果:
delegate to parent: java.lang.Object
load by system: java.lang.Object
load by self: test.TargetCaller4
load by self: test.Intf
---------
delegate to parent: test.Impl
load by system: java.lang.Object
load by parent: test.Intf
load by parent: test.Impl
...
v classLoader: ParentLoader
test.Impl@5674cd4d
变量v的ClassLoader是ParentLoader,它的定义类型没法知道是哪个ClassLoader加载的,但返回时的类型很明显是由ChildLoader来加载的,转换却没出错。
稍微修改一下,换成TargetCaller5测试
TargetCaller5.java:
package test;
public class TargetCaller5 {
public Intf test() {
Object v = new Impl();
System.out.println("v classLoader: " + v.getClass().getClassLoader());
return (Intf) v;
}
}
输出结果:
delegate to parent: java.lang.Object
load by system: java.lang.Object
load by self: test.TargetCaller5
load by self: test.Intf
---------
delegate to parent: test.Impl
load by system: java.lang.Object
load by parent: test.Intf
load by parent: test.Impl
...
v classLoader: ParentLoader
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at test.TargetTest.main(TargetTest.java:26)
Caused by: java.lang.ClassCastException: test.Impl cannot be cast to test.Intf
at test.TargetCaller5.test(TargetCaller5.java:8)
... 5 more
这次把变量v的类型定义为Object后,返回时的转换就出错了,因为转换是在两个不同的ClassLoader加载的Intf之间发生的。
结合TargetCaller4的结果来看,就会发现很奇怪,TargetCaller4貌似直接把返回时的类型转换给跳过了。
一旦出现LinkageError:
解决方法: