Сканирование jar-файлов пути к классам манифеста в Embeeded Tomcat

У меня есть встроенное приложение Tomcat, упакованное как исполняемый (тонкий) jar с несколькими внешними jar зависимостями.

Процесс сборки генерирует META-INF/MANIFEST.MF с полями заголовка Main-Class и Class-Path (с записью для каждой зависимости времени выполнения).

Я хочу выполнить приложение, используя простой java -jar my_app.jar, но я не могу заставить Tomcat сканировать эти зависимые файлы jar (чтобы обнаружить TLD или @HandlesTypes классы, такие как Spring WebApplicationInitializer).

Я настраиваю сканирование jar следующим образом:

StandardJarScanner jarScanner = (StandardJarScanner) ctx.getJarScanner();
jarScanner.setScanBootstrapClassPath(true);
jarScanner.setScanClassPath(true);

И у всех банок есть папка META-INF, но сканер их полностью игнорирует.

Любые идеи?

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


person idelvall    schedule 10.03.2016    source источник


Ответы (1)


Tomcat получает URL-адреса jar для сканирования, многократно вызывая URLClassLoader.getURLS() в иерархии загрузчика классов (снизу вверх)

Проблема возникает для загрузчика системного класса, поскольку URLClassLoader.getURLS() не возвращает jar-файлы пути к классам, когда приложение Java выполняется как java -jar <executable-jar>

См.: Как загрузчик классов загружает ссылки на классы в путь к классам манифеста?

В предыдущем посте предлагалось использовать отражение для доступа к частному полю в экземпляре системного загрузчика классов, но это создает несколько проблем:

  • Это присоединение может быть запрещено администратором безопасности
  • Решение зависит от реализации

Поэтому я придумал другой способ:

  1. Для данного загрузчика классов перечислите все доступные манифесты cl.getResources("META-INF/MANIFEST.MF"). Эти манифесты могут быть jar-файлами, управляемыми текущим загрузчиком классов или его дочерними загрузчиками классов.
  2. Сделайте то же самое для его родительского загрузчика классов
  3. Вернуть набор банок тех, что проявляются в (1), но не в (2)

Единственным требованием для работы этого метода является то, что jar-файлы в пути к классам должны иметь манифест, чтобы их можно было вернуть (не так много вопросов).

/**
 * Returns the search path of URLs for loading classes and resources for the 
 * specified class loader, including those referenced in the 
 * {@code Class-path} header of the manifest of a executable jar, in the 
 * case of class loader being the system class loader. 
 * <p>
 * Note: These last jars are not returned by 
 * {@link java.net.URLClassLoader#getURLs()}.
 * </p>
 * @param cl
 * @return 
 */
public static URL[] getURLs(URLClassLoader cl) {
    if (cl.getParent() == null || !(cl.getParent() 
            instanceof URLClassLoader)) {
        return cl.getURLs();
    }
    Set<URL> urlSet = new LinkedHashSet();
    URL[] urLs = cl.getURLs();
    URL[] urlsFromManifest = getJarUrlsFromManifests(cl);
    URLClassLoader parentCl = (URLClassLoader) cl.getParent();
    URL[] ancestorUrls = getJarUrlsFromManifests(parentCl);

    for (int i = 0; i < urlsFromManifest.length; i++) {
        urlSet.add(urlsFromManifest[i]);
    }
    for (int i = 0; i < ancestorUrls.length; i++) {
        urlSet.remove(ancestorUrls[i]);
    }
    for (int i = 0; i < urLs.length; i++) {
        urlSet.add(urLs[i]);
    }
    return urlSet.toArray(new URL[urlSet.size()]);
}

/**
 * Returns the URLs of those jar managed by this classloader (or its 
 * ascendant classloaders) that have a manifest
 * @param cl
 * @return 
 */
private static URL[] getJarUrlsFromManifests(ClassLoader cl) {
    try {
        Set<URL> urlSet = new LinkedHashSet();
        Enumeration<URL> manifestUrls = 
                cl.getResources("META-INF/MANIFEST.MF");
        while (manifestUrls.hasMoreElements()) {
            try {
                URL manifestUrl = manifestUrls.nextElement();
                if(manifestUrl.getProtocol().equals("jar")) {
                    urlSet.add(new URL(manifestUrl.getFile().substring(0, 
                            manifestUrl.getFile().lastIndexOf("!"))));
                }
            } catch (MalformedURLException ex) {
                throw new AssertionError();
            }
        }
        return urlSet.toArray(new URL[urlSet.size()]);
    } catch (IOException ex) {
        throw new RuntimeException(ex);
    }
}

Зарегистрированная проблема Tomcat: https://bz.apache.org/bugzilla/show_bug.cgi?id=59226

person idelvall    schedule 23.03.2016