java—类装入器在ApacheTomcat中部署的war中如何工作

jv4diomz  于 2021-10-10  发布在  Java
关注(0)|答案(1)|浏览(422)

在下面的场景中,我对类加载的工作方式有点困惑。下面是我对类加载的了解。
根据tomcat文档。类加载按以下顺序进行
jvm的引导类
web应用程序的web inf/类
/web应用程序的web inf/lib/*.jar
系统类加载器类
公共类装入器类-->这是tomcat的lib dir,我的外部jar在这里
如果它不是一个web应用程序,则类加载按以下顺序进行
引导加载程序
系统加载器
程序加载器
现在,在我的例子中,我有一个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加载一个类时,它是使用基于web应用程序的类加载(如我所指示的第一个)还是使用java类加载(如我所指示的第二个)。那么,得到一份工作的原因是什么呢 ClassNotFoundException 尝试从外部jar加载时。难道不是吗 GenericResult 类装入器无法在中找到类 WEB-INF/Classes ??
例外情况:

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) ~[?:?]
uqdfh47h

uqdfh47h1#

ObjectInputStream#readObject 电话 ObjectInputStream#resolveClass 检索对象。默认实现使用当前线程堆栈上的第一个加载程序,因此在您的案例中使用公共加载程序。类加载器只能找到自己的类和它的祖先的类,因此它无法在web应用程序中找到这些类。
如果要反序列化web应用程序中的对象,则需要使用web应用程序的类加载器,该类加载器在当前线程上设置为上下文类加载器。因此,您需要扩展 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无法从您的应用程序加载类,因为它使用通常的算法:
询问父(系统)类加载器,
看看你自己的位置。
另一方面,web应用程序类加载器使用以下算法:
询问引导类加载器,
看看你自己的位置,
询问父(公共)类加载器。

相关问题