简体   繁体   English

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

[英]ObjectInputStream#readObject raises ClassNotFoundException with External Jar classes

So I have a Spring Boot application that loads external jars from the paths below:所以我有一个 Spring 引导应用程序,它从以下路径加载外部 jars:

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

The main jar doesn't know external jars at compile time.主 jar 在编译时不知道外部 jars。 External jars are loaded like "plugins" or "addons" by specifying通过指定外部 jars 像“插件”或“插件”一样加载

-Dloader.path=...

All of external jars depend on an interface from "main-0.0.1-SNAPSHOT.jar", and they are supposed to do object serializations more or less.所有外部 jars 都依赖于来自“main-0.0.1-SNAPSHOT.jar”的接口,它们应该或多或少地进行 object 序列化。 The interface is called Configurable , and it provides two default methods like these:该接口称为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();
}

Classes in external jars implement this interface, and they call readFile() and writeFile() .外部 jars 中的类实现了这个接口,它们调用readFile()writeFile()

writeFile() works perfectly fine and doesn't seem to cause any problem; writeFile()工作得很好,似乎没有引起任何问题; readFile() , however, throws a ClassNotFoundException , and that's what I'm trying to figure out.但是, 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)

After some testing it seems to me that ClassNotFoundException is thrown by Class.forName() because the default ClassLoader has a hard time looking for moe.ofs.addon.navdata.domain.Navaid , which is the class I'm trying to deserialize.经过一些测试,在我看来ClassNotFoundException是由 Class.forName() 引发的,因为默认的 ClassLoader 很难找到moe.ofs.addon.navdata.domain.Navaid ,这是我正在尝试反序列化的 class 。

Navaid implements Serializable , and it also has a static final long serialVersionUID . Navaid implements Serializable ,它还有一个static final long serialVersionUID

I had hoped that I could solve this by setting a context class loader for current thread, so that ObjectInputStream will use Spring Boot class loader to resolve Navaid class: I had hoped that I could solve this by setting a context class loader for current thread, so that ObjectInputStream will use Spring Boot class loader to resolve Navaid class:

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

This, when printed out, gives something like这在打印出来时会给出类似的东西

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

Except that ObjectInputStream#readObject still throws ClassNotFoundException .除了ObjectInputStream#readObject仍然抛出ClassNotFoundException If I explicitly make a call to load Navaid class from Spring Boot loader such as:如果我明确调用从 Spring 引导加载程序加载 Navaid class ,例如:

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

It returns a Navaid instance without any issue.它返回一个Navaid实例,没有任何问题。

And as expected, when directly calling正如预期的那样,直接调用时

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

a ClassNotFoundException is thrown, even if the thread context loader has been explicitly set to LaunchedURLClassLoader ;即使线程上下文加载器已显式设置为LaunchedURLClassLoader ,也会引发ClassNotFoundException ObjectInputStream#readObject always tries to resolve the class by making a call to system default classloader to load the class. ObjectInputStream#readObject总是尝试通过调用系统默认类加载器来加载 class 来解析 class。

Then I tried to load an ObjectInputStream using LaunchedURLClassLoader , but the instance still used Class.forName() from system default class loader.然后我尝试使用LaunchedURLClassLoader加载ObjectInputStream ,但该实例仍然使用系统默认 class 加载器中的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

Any input is appreciated.任何输入表示赞赏。 Thanks in advance.提前致谢。

As far as i know, you should override the method resolveClass on ObjectInputStream据我所知,您应该覆盖ObjectInputStream上的方法resolveClass

Something like that:像这样的东西:

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();
}

Never tried it myself, but it is worth a shot.自己从未尝试过,但值得一试。

There is also http://commons.apache.org/proper/commons-io/javadocs/api-2.4/org/apache/commons/io/input/ClassLoaderObjectInputStream.html if you aready have commons-io in your project. There is also http://commons.apache.org/proper/commons-io/javadocs/api-2.4/org/apache/commons/io/input/ClassLoaderObjectInputStream.html if you aready have commons-io in your project.

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 ObjectInputStream.readObject()中的ClassNotFoundException - ClassNotFoundException in ObjectInputStream.readObject() ObjectInputStream readObject():ClassNotFoundException - ObjectInputStream readObject(): ClassNotFoundException ObjectInputStream#readObject抛出java.io.EOFException - ObjectInputStream#readObject throws java.io.EOFException 来自ObjectInputStream的readObject时发生ClassNotFoundException - ClassNotFoundException while readObject from ObjectInputStream 当我在ObjectInputStream上调用readObject()时发生ClassNotFoundException - ClassNotFoundException when I call readObject() on a ObjectInputStream 如何在ObjectInputStream.readObject中绕过ClassNotFoundException? - How to bypass ClassNotFoundException while ObjectInputStream.readObject? 在Android中调用ObjectInputStream.readobject时发生ClassNotFoundException - ClassNotFoundException happens during calling ObjectInputStream.readobject in Android objectInputstream的readObject方法在更改类包时抛出ClassNotFoundException - readObject method of objectinputstream throw ClassNotFoundException when changed class package 为什么我用ObjectInputStream.readObject()得到ClassNotFoundException? - Why am I getting ClassNotFoundException with ObjectInputStream.readObject()? while 循环中的 ObjectInputStream readObject - ObjectInputStream readObject in while Loop
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM