【问题标题】:Scanning manifest classpath jars in Embeeded Tomcat扫描嵌入式 Tomcat 中的清单类路径 jar
【发布时间】:2016-06-25 15:02:55
【问题描述】:

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

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

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

我是这样配置jar扫描的:

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

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

有什么想法吗?

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

【问题讨论】:

    标签: java spring-boot servlet-3.0 embedded-tomcat-8


    【解决方案1】:

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

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

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

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

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

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

    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

    【讨论】:

      猜你喜欢
      • 2012-10-25
      • 1970-01-01
      • 2017-11-20
      • 2020-02-21
      • 1970-01-01
      • 2018-04-10
      • 2014-05-28
      • 2017-11-19
      • 2020-01-23
      相关资源
      最近更新 更多