在Embeeded Tomcat中扫描清单类路径jar

时间:2016-03-10 16:30:37

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

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

构建过程生成带有标头字段META-INF/MANIFEST.MFMain-Class的{​​{1}}(每个运行时依赖项都有一个条目)。

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

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

WebApplicationInitializer

所有的jar都有一个StandardJarScanner jarScanner = (StandardJarScanner) ctx.getJarScanner(); jarScanner.setScanBootstrapClassPath(true); jarScanner.setScanClassPath(true); 文件夹,但扫描程序完全忽略它们。

有什么想法吗?

  

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

1 个答案:

答案 0 :(得分:3)

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

系统类加载器出现问题,因为当URLClassLoader.getURLS()

执行java应用程序时,java -jar <executable-jar>不返回类路径jar

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

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

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

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

  1. 对于给定的类加载器,枚举所有可用的清单cl.getResources("META-INF/MANIFEST.MF")。这些清单可以是由当前类加载器或其上级类加载器管理的jar。
  2. 对其父类加载器
  3. 执行相同操作
  4. 返回(1)中显示的那些罐子,但不返回(2)
  5. 此方法的唯一要求是,类路径中的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