2

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

构建过程会生成一个META-INF/MANIFEST.MF带有头字段Main-ClassClass-Path(每个运行时依赖项都有一个条目)。

我想使用 simple 执行应用程序java -jar my_app.jar,但我无法让 Tomcat 扫描这些依赖的 jars(以便发现 TLD 或@HandlesTypesSpring 等类WebApplicationInitializer)。

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

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

所有的罐子都有一个META-INF文件夹,但扫描仪完全忽略了它们。

有任何想法吗?

注意:我可以使用不同的方法(fat jar,从 maven 运行,...)来完成这项工作,但我有兴趣让它像任何其他 java 应用程序一样以这种方式工作。

4

1 回答 1

3

Tomcat 通过在类加载器层次结构中重复调用来获取要扫描的 jar URL URLClassLoader.getURLS()(自下而上)

系统类加载器出现问题,因为URLClassLoader.getURLS()在执行 java 应用程序时不返回类路径 jarjava -jar <executable-jar>

请参阅:类加载器如何在清单类路径中加载类引用?

在上一篇文章中建议使用反射来访问系统类加载器实例中的私有字段,但这会带来几个问题:

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

于是我想出了另一种方法:

  1. 对于给定的类加载器,枚举所有可用的清单cl.getResources("META-INF/MANIFEST.MF")。这些清单可以是由当前类加载器或其上层类加载器管理的 jar。
  2. 对其父类加载器执行相同的操作
  3. 返回 (1) 中但不在 (2) 中的那些清单的 jar 集

此方法工作的唯一要求是类路径中的 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

于 2016-03-23T17:57:27.520 回答