Итак, у меня есть приложение 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=...
Все внешние банки зависят от интерфейса из 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
class:
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
Это при печати out, дает что-то вроде
Thread.currentThread().getContextClassLoader() = org.springframework.boot.loader.LaunchedURLClassLoader@7a0b4753
За исключением того, что ObjectInputStream#readObject
все еще выбрасывает ClassNotFoundException
. Если я явно сделаю вызов для загрузки класса Navaid из загрузчика Spring Boot, например:
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
Любой ввод приветствуется. Заранее спасибо.