在嵌入式中扫描清单类路径 jar Tomcat

Scanning manifest classpath jars in Embeeded Tomcat

我有一个嵌入式 Tomcat 应用程序打包为可执行文件(瘦)jar,具有多个外部 jar 依赖项。

构建过程生成一个 META-INF/MANIFEST.MF,其中包含 header 个字段 Main-ClassClass-Path(每个运行时依赖项都有一个条目)。

我想使用一个简单的 java -jar my_app.jar 来执行应用程序,但是我 无法让 Tomcat 扫描这些依赖的 jar(为了发现TLD 或 @HandlesTypes 类 如 Spring WebApplicationInitializer).

我正在以这种方式配置 jar 扫描:

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

而且所有的jar都有一个META-INF文件夹,但是扫描器完全忽略了它们。

有什么想法吗?

Note: I can make this work using different approaches (fat jar, running from maven, ...) but I am interested in making it work this way, as any other java application.

Tomcat 通过在 class 加载器层次结构(自下而上)

中重复调用 URLClassLoader.getURLS() 来获取要扫描的 jar URL

系统 class 加载程序出现问题,因为 URLClassLoader.getURLS() 不 return 当 java 应用程序作为 java -jar <executable-jar>

参见:How does a classloader load classes reference in the manifest classpath?

前面post建议使用反射访问系统classloader实例中的私有字段,但这会带来几个问题:

  • 安全管理员可以禁止此加入
  • 解决方案取决于实现

所以我想出了另一种方法:

  1. 对于给定的 classloader 枚举所有可用的清单 cl.getResources("META-INF/MANIFEST.MF")。这些清单可以是当前 class 加载器或其后代 classloader.
  2. 管理的 jar。
  3. 对其父级执行相同的操作 classloader
  4. Return 在 (1) 中出现但在 (2) 中不出现的一组 jar

此方法起作用的唯一要求是 class 路径中的 jar 必须有一个清单才能被 returned(没什么好问的)。

/**
 * 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