Тема с загрузкой классов уже достаточно заезжена и теория по ней гуглится легко. За пару минут нашел
официальные доки,
довольно подробная статья из кого-то блога,
запись выступления на конференции JUG.RU
Определимся с начальными условиями. Как я понял, есть папка с jar-файлами. Необходимо пройтись по всем jar-файлам и на выходе получить список классов, реализующих
Module
. Что-то вроде
List<Module> discoverModules(File dir) {}
Будем считать, что 1 jar-файл может содержать только один модуль (здесь и далее, "модуль" - это класс, реализующий интерфейс
Module
). Здесь самый нетривиальный вопрос, как этот модуль найти внутри архива. Самый простой (для начала) способ - положить в каждый jar-файл по определенному пути некий конфиг, в котором указать путь к модулю. И такой конфиг в jar-файле уже есть - стандартный META-INF. В простейшем случае у нас получается jar-файл из двух файлов с такой структурой:
MyMod.jar
├───META-INF
│ └───MANIFEST.MF
└───mymod
└───MyMod.class
Где
MANIFEST.MF
содержит строчку
Main-Class: mymod.MyMod
т.е. путь к классу MyMod.
Класс MyMod нужно собирать отдельным проектом, добавляя в зависимость проект, содержащий интерфейс
Module
. Содержимое MyMod.java
package mymod;
public class MyMod implements Module {
public int run() {
System.out.println("MyMod loaded!");
}
}
Будем считать, что MyMod.jar уже собран и лежит (возможно, вместе с другими модулями) в директории "/modules".
Ну вот, это были начальные условия =) Теперь к сути вопроса.
public class ModuleLoader {
public static void main(String[] args) {
List<Module> mods = new ModuleLoader().discoverModules(new File("/modules"));
for(Module mod : mods)
mod.run();
}
public List<Module> discoverModules(File dir) {
List<File> jarFiles = Stream.of(dir.listFiles())
.filter(f -> f.getName().endsWith(".jar"))
.collect(Collectors.toList());
URL[] moduleUrls = jarFiles.stream().map(this::toUrl).toArray(URL[]::new);
URLClassLoader classLoader = new URLClassLoader(moduleUrls, getClass().getClassLoader());
return jarFiles.stream()
.map(this::getMainClassName)
.map(name -> {
try {
return (Module) classLoader.loadClass(name).newInstance();
} catch(Exception e) {
throw new RuntimeException(e);
}
})
.collect(Collectors.toList());
}
private String getMainClassName(File file) {
try(JarFile jar = new JarFile(file)){
return (String) jar.getManifest().getMainAttributes().get("Main-Class");
} catch(IOException e) {
throw new RuntimeException(e);
}
}
private URL toUrl(File file) {
try {
return file.toURI().toURL();
} catch(MalformedURLException e) {
throw new RuntimeException(e);
}
}
}