当前位置: 首页 > 知识库问答 >
问题:

ObjectInputStream#readObject使用外部Jar类引发ClassNotFoundException

方寒
2023-03-14

所以我有一个Spring Boot应用程序,它从以下路径加载外部jar:

java -cp "main-0.0.1-SNAPSHOT.jar" -Dloader.path="%USERPROFILE%\Addons\" -Dloader.main=moe.ofs.backend.BackendApplication org.springframework.boot.loader.PropertiesLauncher

主jar在编译时不知道外部jar。外部jar通过指定像“插件”或“插件”一样加载

Dloader.path

所有外部jar都依赖于“main-0.0.1-SNAPSHOT. jar”的接口,它们应该或多或少地执行对象序列化。该接口称为Configurable,它提供了两个默认方法,如下所示:

default <T extends Serializable> void writeFile(T object, String fileName) throws IOException {
    Path configFilePath = configPath.resolve(fileName + ".data");
    FileOutputStream fileOutputStream = new FileOutputStream(configFilePath.toFile());
    ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
    objectOutputStream.writeObject(object);
    objectOutputStream.close();
}

default <T extends Serializable> T readFile(String fileName) throws IOException, ClassNotFoundException {
    Path configFilePath = configPath.resolve(fileName + ".data");
    FileInputStream fileInputStream = new FileInputStream(configFilePath.toFile());
    ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
    return (T) objectInputStream.readObject();
}

外部jar中的类实现了这个接口,它们调用readFile()WriteFile()

writeFile()工作正常,似乎不会引起任何问题<然而,code>readFile()抛出了一个ClassNotFoundException,这就是我想弄明白的。

java.lang.ClassNotFoundException: moe.ofs.addon.navdata.domain.Navaid
    at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:355)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:348)
    at java.io.ObjectInputStream.resolveClass(ObjectInputStream.java:719)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1922)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1805)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2096)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1624)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:464)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)
    at java.util.ArrayList.readObject(ArrayList.java:797)
    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 java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1170)
    at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2232)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2123)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1624)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:464)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)
    at moe.ofs.backend.Configurable.lambda$readFile$0(Configurable.java:186)
    at java.lang.Thread.run(Thread.java:748)

经过一些测试,在我看来ClassNotFoundException是由Class.forName()抛出的,因为默认的ClassLoader很难找到moe.ofs.addon.navdata.domain.Navaid,这是我试图反序列化的类。

导航设备实现可串行化,并且它还有一个静态的最终长serialVersionUID。

我曾希望可以通过为当前线程设置上下文类加载器来解决这个问题,以便ObjectInputStream将使用Spring Boot类加载器来解析Navaid类:

Thread.currentThread().setContextClassLoader(getClass().getClassLoader());

当打印出来时,会显示

Thread.currentThread().getContextClassLoader() = org.springframework.boot.loader.LaunchedURLClassLoader@7a0b4753

除了ObjectInputStream#readObject仍然抛出ClassNotFoundException。如果我显式调用从Spring Boot加载器加载Navaid类,例如:

getClass().getClassLoader().loadClass("moe.ofs.addon.navdata.domain.Navaid");

它返回一个没有任何问题的导航设备实例。

正如所料,当直接呼叫时

Class.forName("moe.ofs.addon.navdata.domain.Navaid")

即使线程上下文加载器已显式设置为LaunchedURLClassLoader,也会引发一个ClassNotFoundExceptionObjectInputStream#readObject总是通过调用系统默认类加载器来加载类来尝试解析类。

然后我尝试使用LaunchedURLClassLoader加载ObjectInputStream,但实例仍然使用系统默认类加载器中的Class.forName()

ClassLoader cl = getClass().getClassLoader();

Thread.currentThread().setContextClassLoader(cl);

System.out.println("Thread.currentThread().getContextClassLoader() = " + Thread.currentThread().getContextClassLoader());

Class<?> tClass = getClass().getClassLoader().loadClass("java.io.ObjectInputStream");
System.out.println("tClass = " + tClass);

Path configFilePath = configPath.resolve(fileName + ".data");
FileInputStream fileInputStream = new FileInputStream(configFilePath.toFile());

Constructor<?> constructor = tClass.getConstructor(InputStream.class);

ObjectInputStream objectInputStream = (ObjectInputStream) constructor.newInstance(fileInputStream);

objectInputStream.readObject();  // throws ClassNotFoundException

欢迎您的任何意见。提前谢谢。

共有1个答案

皇甫福
2023-03-14

据我所知,您应该重写ObjectInputStream上的方法

诸如此类:

default <T extends Serializable> T readFile(String fileName, ClassLoader loader) throws IOException, ClassNotFoundException {
    Path configFilePath = configPath.resolve(fileName + ".data");
    FileInputStream fileInputStream = new FileInputStream(configFilePath.toFile());
    ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream){
       protected Class<?> resolveClass(ObjectStreamClass desc)
                     throws IOException, ClassNotFoundException {
          try {
              return Class.forName(desc.getName(), false, loader);
          } catch(ClassNotFoundException cnfe) {
              return super.resolveClass(desc);
          }
       }
   };
   return (T) objectInputStream.readObject();
}

我自己从未尝试过,但值得一试。

如果您的项目中有Commons io,也http://commons.apache.org/proper/commons-io/javadocs/api-2.4/org/apache/commons/io/input/ClassLoaderObjectInputStream.html。

https://github.com/apache/commons-io/blob/master/src/main/java/org/apache/commons/io/input/ClassLoaderObjectInputStream.java

 类似资料:
  • 问题内容: 是否有可能从while循环中读取内容,该内容将因套接字超时引发的异常而终止 问题答案: 当您说“不工作”时,由于编译器消息中所述的原因,您真正的意思是“不编译”:不是表达式,也不能在条件中声明变量。 但是该代码仍然无效。读取到任意流的末尾的正确方法是catch ,例如,如下所示: 请注意,在意见建议,以测试返回值是 不 正确的。仅当您写了时,它才会返回。

  • 本文向大家介绍Java ObjectInputStream readObject()方法(带示例),包括了Java ObjectInputStream readObject()方法(带示例)的使用技巧和注意事项,需要的朋友参考一下 ObjectInputStream类方法 readObject()方法在java.io包中可用。 readObject()方法用于从此ObjectInputStream

  • 问题内容: 我陷入了这个非常奇怪的问题。在客户端中,我传递的对象是 在服务器中,我正在读取对象 我的问题是为什么我会出现EOF异常。我对对象输入流的理解是,当我调用readObject()时应该阻塞直到获得对象,这样才能知道是否达到了eof?请帮忙! 这就是我创建对象流的方式 另外,在我写完对象并刷新之后,我应该关闭流。我不会关闭它,因为对象是从代码的不同部分开始一个接一个地定期编写的。 问题答案

  • 我在面向 Web 开发人员的 Eclipse Java EE IDE 中有一个专家项目。在运行 Tomcat 时,我收到一个类的错误,我可以看到我已经包含该类。 我的问题是-因为Tomcat Apache,我必须将这些JAR添加到其他位置吗? 错误: 严重:Servlet /Resource抛出load()异常java.lang.ClassNotFoundException:org.odata4j

  • 我正在完成一个项目,我必须将它部署为一个jar文件,在eclipse中它工作得很好,我的项目依赖于两个库,这两个库也都依赖于dll文件。我已经将这些DLL的路径添加到每个外部JAR的本机库位置。然后,我使用eclipse可运行的jar文件导出向导将project导出为一个jar文件我在指定的位置得到了jar文件,它运行了,但是当我单击一个调用我得到的库之一的按钮时,我得到了java.lang.Un

  • 我有一个jar文件,它有下面的类来加载一些属性- 在我的Spring启动项目中,上面的class/jar是一个依赖项。 我如何指定在哪里寻找这个属性“公共列表”来加载我的项目? 或 我是否需要修改类/jar,并使用属性源注释指定在哪里查找这些公共列表值的属性文件? 我bootstrap.properties以下行- xyz.yaml配置服务器 通过查看我的spring boot应用程序的env属性