簡體   English   中英

ObjectInputStream#readObject 使用外部 Jar 類引發 ClassNotFoundException

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

所以我有一個 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

主 jar 在編譯時不知道外部 jars。 通過指定外部 jars 像“插件”或“插件”一樣加載

-Dloader.path=...

所有外部 jars 都依賴於來自“main-0.0.1-SNAPSHOT.jar”的接口,它們應該或多或少地進行 object 序列化。 該接口稱為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();
}

外部 jars 中的類實現了這個接口,它們調用readFile()writeFile()

writeFile()工作得很好,似乎沒有引起任何問題; 但是, 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 ,這是我正在嘗試反序列化的 class 。

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:

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

這在打印出來時會給出類似的東西

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

除了ObjectInputStream#readObject仍然拋出ClassNotFoundException 如果我明確調用從 Spring 引導加載程序加載 Navaid class ,例如:

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

它返回一個Navaid實例,沒有任何問題。

正如預期的那樣,直接調用時

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

即使線程上下文加載器已顯式設置為LaunchedURLClassLoader ,也會引發ClassNotFoundException ObjectInputStream#readObject總是嘗試通過調用系統默認類加載器來加載 class 來解析 class。

然后我嘗試使用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

任何輸入表示贊賞。 提前致謝。

據我所知,您應該覆蓋ObjectInputStream上的方法resolveClass

像這樣的東西:

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

自己從未嘗試過,但值得一試。

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM