所以我有一个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。通过指定
,可以像“插件”或“附件”一样加载外部罐子-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()
可以正常工作,似乎不会引起任何问题; 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
,这是我要反序列化的类。
Navaid implements Serializable
,它也有一个static final long serialVersionUID
。
我希望我可以通过为当前线程设置上下文类加载器来解决此问题,以便ObjectInputStream
将使用Spring Boot类加载器来解析Navaid
类:
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
[当打印出来时,给出类似
Thread.currentThread().getContextClassLoader() = org.springframework.boot.loader.LaunchedURLClassLoader@7a0b4753
[除了ObjectInputStream#readObject
仍会抛出ClassNotFoundException
。如果我明确调用从Spring Boot loader加载Navaid类,例如:
getClass().getClassLoader().loadClass("moe.ofs.addon.navdata.domain.Navaid");
它返回一个Navaid
实例,没有任何问题。
和预期的,当直接调用时
Class.forName("moe.ofs.addon.navdata.domain.Navaid")
a ClassNotFoundException
被抛出,即使线程上下文加载器已显式设置为LaunchedURLClassLoader
; ObjectInputStream#readObject
始终尝试通过调用系统默认的类加载器来加载该类,以解析该类。
然后我尝试使用ObjectInputStream
加载LaunchedURLClassLoader
,但是实例仍从系统默认的类加载器中使用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
感谢任何输入。预先感谢。
据我所知,您应该覆盖resolveClass
上的方法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 {
return Class.forName(desc.getName(), false, loader);
}
};
return (T) objectInputStream.readObject();
}
从未尝试过自己,但是值得一试。
如果您的项目中有commons-io,也有http://commons.apache.org/proper/commons-io/javadocs/api-2.4/org/apache/commons/io/input/ClassLoaderObjectInputStream.html。