0

我对以下场景中类加载的工作方式有点困惑。现在这是我对类加载的了解。

根据 Tomcat文档。类加载按以下顺序进行

  1. JVM 的引导类
  2. WEB-INF/Web 应用程序的类
  3. /WEB-INF/lib/*.jar 您的 Web 应用程序
  4. 系统类加载器类
  5. 通用类加载器类 --> 这是 tomcat 的 lib 目录,我的外部 jar 在这里

如果它不是一个网络应用程序,那么类加载按这个顺序发生

  1. 引导加载程序

  2. 系统加载器

  3. 应用程序加载器

现在,就我而言,我有一个使用外部 jar 读取一些序列化数据的 Web 应用程序。尝试读取序列化数据时,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 加载时获得 a 的原因可能是什么。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) ~[?:?]
4

1 回答 1

1

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加载器无法从您的应用程序加载类,因为它使用通常的算法:

  1. 询问父(System)类加载器,
  2. 看看你自己的位置。

另一方面,Web 应用程序类加载器使用以下算法:

  1. 询问Bootstrap类加载器,
  2. 看看自己的位置,
  3. 询问父 ( Common ) 类加载器。
于 2021-06-21T14:14:23.947 回答