Создайте classpath из JAR внутри файла WAR

Я пытаюсь динамически извлечь конфигурацию из данного файла WAR. Моя цель - найти все классы, которые реализуют какой-то интерфейс (Parameter).

Файл war не находится на пути к классам, поэтому я создаю временный загрузчик классов для проверки его классов.

URL webClasses = new URL("jar","","file:" + new File(path).getAbsolutePath() + "!/WEB-INF/classes/");
URLClassLoader cl = URLClassLoader.newInstance(new URL[] { webClasses });
Reflections reflections = new Reflections(ClasspathHelper.forClassLoader(cl), new SubTypesScanner(), cl);
Set<Class<? extends Parameter>> params = reflections.getSubTypesOf(Parameter.class);

Это отлично работает. Я нахожу все реализации Parameter в моем веб-приложении.

Теперь я хотел бы сделать то же самое для каждой банки в WEB-INF/lib, К сожалению, я не нашел способа построить загрузчик классов в этом случае. Загрузчик классов, похоже, игнорирует URL-адреса jar (jar:<url>!/WEB-INF/lib/{entry}.jar).

Когда я запускаю приведенный ниже код, класс Reflections не находит:

Set<URL> libs = findLibJars(warUrl));
// The URLs are in the following format : {myWar}.war!/WEB-INF/lib/{myLib}.jar
URLClassLoader cl = URLClassLoader.newInstance(urls.toArray(new URL[urls.size()]));
cl.loadClass("my.company.config.AppParameter");

Если возможно, я бы хотел избежать необходимости извлекать WEB-INF/lib/*.jar из файла WAR и проанализируйте их отдельно.

Я что-то здесь упускаю? (другой способ сделать это, другой способ создать загрузчик классов, ... любое руководство поможет). Может быть, это можно сделать без использования библиотеки отражений?

1 ответ

Решение

Это конечно возможно. Вот ужасный пример этого:

String warName = "wlaj.war";
ClassLoader loader = new ClassLoader() {
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // This probably needs fixing:
        String fileName = name.replace('.', '/') + ".class";
        try {
            try (ZipFile zf = new ZipFile(warName)) {
                ZipEntry jar = zf.getEntry("WEB-INF/lib/jlaj.jar");
                if (jar == null)
                    throw new ClassNotFoundException("No jlaj.jar");
                try (ZipInputStream jarInput = new ZipInputStream(zf.getInputStream(jar))) {
                    for (ZipEntry cl; (cl = jarInput.getNextEntry()) != null; ) {
                        if (fileName.equals(cl.getName())) {
                            ByteArrayOutputStream data = new ByteArrayOutputStream();
                            byte[] buffer = new byte[4096];
                            for (int len; (len = jarInput.read(buffer)) != -1; ) {
                                data.write(buffer, 0, len);
                            }
                            buffer = data.toByteArray();
                            return defineClass(name, buffer, 0, buffer.length);
                        }
                    }
                }
            }
            throw new ClassNotFoundException();
        } catch (IOException ex) {
            throw new ClassNotFoundException("Error opening class file", ex);
        }
    }
};
loader.loadClass("jlaj.NewJFrame");

Проблемы:

  1. Нужен элегантный надежный способ преобразовать имя класса в путь к файлу внутри JAR. В моем случае "jlaj.NewJFrame" -> "jlaj/NewJFrame.class", но это становится очень уродливым при попытке загрузить внутренние классы и, возможно, в некоторых других ситуациях, о которых я даже не могу думать.
  2. Чтение данных класса, вероятно, должно принадлежать некоторой утилите readAll метод, потому что это выглядит очень грязно.
  3. Очевидно, что для этого нужен неанонимный класс с именем WAR и внутренним именем JAR, передаваемым конструктору. Или же ZipFile а также ZipEntry, если вы хотите перебрать их внешне.

С положительной стороны, это работает.

Другие вопросы по тегам