class 加载程序如何在部署在 Apache Tomcat 中的 WAR 中工作
How does class loaders work in a WAR deployed in Apache Tomcat
我对 class 加载在以下场景中的工作方式有点困惑。下面是我对 class 加载的了解。
根据 Tomcat docs。 class 加载按以下顺序发生
- Bootstrap class你的 JVM
- WEB-INF/classes 您的 Web 应用程序
- /WEB-INF/lib/*.jar 你的 web 应用程序
- 系统class加载程序classes
- 通用 class 加载器 classes --> 这是 tomcat 的 lib 目录,我的外部 jar 在这里
如果它不是网络应用程序,则 class 加载会按此顺序发生
Bootstrap 装载机
系统加载器
应用程序加载器
现在,就我而言,我有一个 Web 应用程序,它使用外部 jar 读取一些序列化数据。尝试读取序列化数据时,jar 抛出 ClassNotFoundException
。但是,如果我在不使用 jar 的情况下使用自己的序列化逻辑,那么应用程序就可以运行。
这是错误的例子
public GenericResult getGenericResult( ){
GenericResult cachedResult = externalJar.get( "myKey" ); // This jar uses deserialization
return cachedResult;
}
这是工作示例
public GenericResult getGenericResult( ){
// do not mind resource closing and all as this is a just to show how I did it
FileInputStream fileIn = new FileInputStream(filepath);
ObjectInputStream objectIn = new ObjectInputStream(fileIn);
GenericResult cachedResult = (GenericResult)objectIn.readObject();
return cachedResult;
}
外部 jar 是驻留在 tomcats lib 目录中的提供的 jar。我想澄清的是,当从这个外部 jar 加载 class 时,它是使用我首先指出的基于 Web 应用程序的 class 加载还是 java class按照我指示的方式加载第二个。尝试从外部 jar 加载时获得 ClassNotFoundException
的原因可能是什么。 GenericResult
class 不应该被 WEB-INF/Classes
的 class 加载程序找到吗??
异常:
java.lang.ClassNotFoundException: com.example.result.GenericResult
at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:435) ~[?:?]
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:589) ~[?:?]
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522) ~[?:?]
at java.base/java.lang.Class.forName0(Native Method) ~[?:?]
at java.base/java.lang.Class.forName(Class.java:468) ~[?:?]
at java.base/java.io.ObjectInputStream.resolveClass(ObjectInputStream.java:782) ~[?:?]
at java.base/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2028) ~[?:?]
at java.base/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1895) ~[?:?]
at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2202) ~[?:?]
at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1712) ~[?:?]
at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:519) ~[?:?]
at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:477) ~[?:?]
at java.base/java.util.ArrayList.readObject(ArrayList.java:899) ~[?:?]
at java.base/jdk.internal.reflect.GeneratedMethodAccessor1272.invoke(Unknown Source) ~[?:?]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
at java.base/java.lang.reflect.Method.invoke(Method.java:564) ~[?:?]
at java.base/java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1226) ~[?:?]
at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2401) ~[?:?]
at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2235) ~[?:?]
at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1712) ~[?:?]
at java.base/java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2540) ~[?:?]
at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2434) ~[?:?]
at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2235) ~[?:?]
at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1712) ~[?:?]
at java.base/java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2540) ~[?:?]
at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2434) ~[?:?]
at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2235) ~[?:?]
at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1712) ~[?:?]
at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:519) ~[?:?]
at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:477) ~[?:?]
ObjectInputStream#readObject
调用 ObjectInputStream#resolveClass
来检索对象。默认实现使用当前线程堆栈上的第一个加载器,因此在您的情况下它使用 common 加载器。一个 classloader 只能找到他自己的 classes 和它祖先的 classes,所以它在你的 web 应用程序中找不到 classes。
如果你想在你的网络应用程序中反序列化一个对象,你需要使用网络应用程序的class加载器,它被设置为当前线程上的上下文class加载器。因此,您需要像这样扩展 ObjectInputStream
:
public class ClassloaderAwareObjectInputStream extends ObjectInputStream {
public ClassloaderAwareObjectInputStream(InputStream in) throws IOException {
super(in);
}
@Override
protected Class< ? > resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
final ClassLoader tccl = Thread.currentThread().getContextClassLoader();
final String name = desc.getName();
try {
return Class.forName(name, false, tccl);
} catch (ClassNotFoundException ex) {
return super.resolveClass(desc);
}
}
}
编辑: Common classloader 无法从您的应用程序加载 class,因为它使用通常的算法:
- 询问父级(系统)classloader,
- 查看您自己的位置。
另一方面,Web 应用程序 classloader 使用算法:
- 询问 Bootstrap classloader,
- 查看您自己的位置,
- 问家长(普通)classloader.
我对 class 加载在以下场景中的工作方式有点困惑。下面是我对 class 加载的了解。
根据 Tomcat docs。 class 加载按以下顺序发生
- Bootstrap class你的 JVM
- WEB-INF/classes 您的 Web 应用程序
- /WEB-INF/lib/*.jar 你的 web 应用程序
- 系统class加载程序classes
- 通用 class 加载器 classes --> 这是 tomcat 的 lib 目录,我的外部 jar 在这里
如果它不是网络应用程序,则 class 加载会按此顺序发生
Bootstrap 装载机
系统加载器
应用程序加载器
现在,就我而言,我有一个 Web 应用程序,它使用外部 jar 读取一些序列化数据。尝试读取序列化数据时,jar 抛出 ClassNotFoundException
。但是,如果我在不使用 jar 的情况下使用自己的序列化逻辑,那么应用程序就可以运行。
这是错误的例子
public GenericResult getGenericResult( ){
GenericResult cachedResult = externalJar.get( "myKey" ); // This jar uses deserialization
return cachedResult;
}
这是工作示例
public GenericResult getGenericResult( ){
// do not mind resource closing and all as this is a just to show how I did it
FileInputStream fileIn = new FileInputStream(filepath);
ObjectInputStream objectIn = new ObjectInputStream(fileIn);
GenericResult cachedResult = (GenericResult)objectIn.readObject();
return cachedResult;
}
外部 jar 是驻留在 tomcats lib 目录中的提供的 jar。我想澄清的是,当从这个外部 jar 加载 class 时,它是使用我首先指出的基于 Web 应用程序的 class 加载还是 java class按照我指示的方式加载第二个。尝试从外部 jar 加载时获得 ClassNotFoundException
的原因可能是什么。 GenericResult
class 不应该被 WEB-INF/Classes
的 class 加载程序找到吗??
异常:
java.lang.ClassNotFoundException: com.example.result.GenericResult
at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:435) ~[?:?]
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:589) ~[?:?]
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522) ~[?:?]
at java.base/java.lang.Class.forName0(Native Method) ~[?:?]
at java.base/java.lang.Class.forName(Class.java:468) ~[?:?]
at java.base/java.io.ObjectInputStream.resolveClass(ObjectInputStream.java:782) ~[?:?]
at java.base/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2028) ~[?:?]
at java.base/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1895) ~[?:?]
at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2202) ~[?:?]
at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1712) ~[?:?]
at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:519) ~[?:?]
at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:477) ~[?:?]
at java.base/java.util.ArrayList.readObject(ArrayList.java:899) ~[?:?]
at java.base/jdk.internal.reflect.GeneratedMethodAccessor1272.invoke(Unknown Source) ~[?:?]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
at java.base/java.lang.reflect.Method.invoke(Method.java:564) ~[?:?]
at java.base/java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1226) ~[?:?]
at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2401) ~[?:?]
at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2235) ~[?:?]
at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1712) ~[?:?]
at java.base/java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2540) ~[?:?]
at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2434) ~[?:?]
at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2235) ~[?:?]
at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1712) ~[?:?]
at java.base/java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2540) ~[?:?]
at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2434) ~[?:?]
at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2235) ~[?:?]
at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1712) ~[?:?]
at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:519) ~[?:?]
at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:477) ~[?:?]
ObjectInputStream#readObject
调用 ObjectInputStream#resolveClass
来检索对象。默认实现使用当前线程堆栈上的第一个加载器,因此在您的情况下它使用 common 加载器。一个 classloader 只能找到他自己的 classes 和它祖先的 classes,所以它在你的 web 应用程序中找不到 classes。
如果你想在你的网络应用程序中反序列化一个对象,你需要使用网络应用程序的class加载器,它被设置为当前线程上的上下文class加载器。因此,您需要像这样扩展 ObjectInputStream
:
public class ClassloaderAwareObjectInputStream extends ObjectInputStream {
public ClassloaderAwareObjectInputStream(InputStream in) throws IOException {
super(in);
}
@Override
protected Class< ? > resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
final ClassLoader tccl = Thread.currentThread().getContextClassLoader();
final String name = desc.getName();
try {
return Class.forName(name, false, tccl);
} catch (ClassNotFoundException ex) {
return super.resolveClass(desc);
}
}
}
编辑: Common classloader 无法从您的应用程序加载 class,因为它使用通常的算法:
- 询问父级(系统)classloader,
- 查看您自己的位置。
另一方面,Web 应用程序 classloader 使用算法:
- 询问 Bootstrap classloader,
- 查看您自己的位置,
- 问家长(普通)classloader.