如何为嵌套JAR文件创建自定义ClassLoader

时间:2020-04-23 07:09:50

标签: java classloader

我正在使用一个Java库,该库在lib包中有一些嵌套的JAR文件。

我有2个问题:

  1. 我在我的IDE中看不到引用的类型(我正在使用JetBrains IntelliJ)
  2. 我当然得到了运行时未定义的类

我了解必须创建并使用自定义的ClassLoader,它可以解决这两个问题吗?
这是获得此结果的推荐方法吗?

JAR文件是意大利政府提供的库,我无法对其进行修改,因为它会随着法规的变化而定期更新。

3 个答案:

答案 0 :(得分:0)

我不知道您在装完罐子后想要做什么。 就我而言,对Servlet示例使用jar动态加载。

    try{
    final URLClassLoader loader = (URLClassLoader)ClassLoader.getSystemClassLoader();
    final Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
    method.setAccessible(true);

    new File(dir).listFiles(new FileFilter() {
        @Override
        public boolean accept(File jar) {

            // load file if it is 'jar' type
            if( jar.toString().toLowerCase().contains(".jar") ){
                try {
                    method.invoke(loader, new Object[]{jar.toURI().toURL()});
                    XMLog.info_arr(logger, jar, " is loaded.");

                    JarInputStream jarFile = new JarInputStream(new FileInputStream(jar));
                    JarEntry jarEntry;

                    while (true) {
                        // load jar file
                        jarEntry = jarFile.getNextJarEntry();
                        if (jarEntry == null) {
                            break;
                        }

                        // load .class file in loaded jar file
                        if (jarEntry.getName().endsWith(".class")) {
                            Class loadedClass = Class.forName(jarEntry.getName().replaceAll("/", "\\.").replace(".class",""));

                            /*
                            * In my case, I load jar file for Servlet.
                            * If you want to use it for other case, then change below codes
                            */
                            WebServlet annotaions =  (WebServlet) loadedClass.getAnnotation(WebServlet.class);

                            // load annotation and mapping if it is Servlet
                            if (annotaions.urlPatterns().length > 0) {
                                ServletRegistration.Dynamic registration = servletContextEvent.getServletContext().addServlet(annotaions.urlPatterns().toString(), loadedClass);
                                registration.addMapping(annotaions.urlPatterns());
                            }
                        }
                    }
                } catch (Exception e) {
                    System.err.println("Can't load classes in jar");
                }
            }
            return false;
        }
    });
} catch(Exception e) {
    throw new RuntimeException(e);
}

答案 1 :(得分:0)

是的,据我所知,标准的ClassLoader不支持嵌套JAR。令人遗憾的是,因为这将是一个非常不错的主意,但是Oracle对此没有给出任何可恶的印象。这是一张18岁的机票:

https://bugs.java.com/bugdatabase/view_bug.do?bug_id=4735639

如果要从其他人那里获得这些JAR,最好的办法是与供应商联系,并要求他们提供标准兼容格式的产品。从您的回答中,我意识到这可能很难实现,但是我仍然会尝试与他们交谈,因为这是正确的做法。我很确定您所处位置的其他所有人都有同样的问题。根据行业标准,这种情况通常会提示您的供应商将Maven存储库用于可交付成果。

如果与供应商的交流失败,则可以在获得JAR时重新打包。我建议为此编写一个自动化脚本,并确保它在每次交付时都运行。您可以将所有.class文件放入一个uber-JAR中,也可以将嵌套的JAR移到封闭的JAR之外。注意事项1:可以有多个同名课程,因此您需要确保选择正确的一个。注意事项2:如果JAR已签名,则您将丢失签名(除非您用自己的签名)。

选项3:您始终可以实现自己的ClassLoader,以从任何地方(甚至从厨房水槽)加载类。

这个人正是这样做的:https://www.ibm.com/developerworks/library/j-onejar/index.html

简短的摘要是,这样的ClassLoader必须执行递归解压缩,这有点麻烦,因为存档实际上是为流访问而不是随机访问而创建的,但除此之外,它非常完美可行。

您可以将他的解决方案用作“包装程序加载器”,以替换您的主类。

就IntelliJ IDEA而言,我不认为它支持现成的功能。最好的办法是如上所述重新打包JAR并将其添加为单独的类路径条目,或者搜索是否有人为嵌套JAR支持编写了插件。

答案 2 :(得分:0)

有趣的是,我刚刚为JesterJ解决了此问题的一个版本,尽管我还需要在jar文件中加载代码依赖关系。 JesterJ(截至今天晚上的提交!)从一个胖子罐运行,并接受一个参数,该参数表示第二个胖子罐,其中包含文档摄取计划(我需要运行的用户代码)的类,依赖项和配置。

我的解决方案的工作方式是,我从Uno-Jar(生产胖罐子的库)中借用了如何将罐子装载到罐子​​中的知识,并在上面装载了我自己的类加载器,以控制罐子的评估顺序。类加载器。

https://github.com/nsoft/jesterj/blob/jdk11/code/ingest/src/main/java/org/jesterj/ingest/Main.java中的关键位如下:

JesterJLoader jesterJLoader;

File jarfile = new File(javaConfig);
URL planConfigJarURL;
try {
  planConfigJarURL = jarfile.toURI().toURL();
} catch (MalformedURLException e) {
  throw new RuntimeException(e); // boom
}

jesterJLoader = (JesterJLoader) ClassLoader.getSystemClassLoader();

ClassLoader loader;
if (isUnoJar) {
  JarClassLoader jarClassLoader = new JarClassLoader(jesterJLoader, planConfigJarURL.toString());
  jarClassLoader.load(null);
  loader = jarClassLoader;
} else {
  loader = new URLClassLoader(new URL[]{planConfigJarURL}, jesterJLoader);
}
jesterJLoader.addExtLoader(loader);

我的JesterJLoader在这里:

https://github.com/nsoft/jesterj/blob/jdk11/code/ingest/src/main/java/org/jesterj/ingest/utils/JesterJLoader.java

尽管如果您乐于简单地委派并依赖主类路径上的现有类(而不是像我正在执行的那样从sub-fat-jar中加载其他依赖项),则可以简单得多。我花了很多力气才允许它首先检查子jar而不是立即委派给父jar,然后必须跟踪已发送到子jar的内容,以避免循环和随后的StackOverflowError ...

还请注意,获得系统类加载器的那行将不是您想要的,我也试着让系统加载器解决一些我的依赖项在类加载中所做的不礼貌的事情。 / p>

如果您决定尝试检查Uno-Jar请注意,此嵌套方案的资源加载可能还很不稳定,并且在https://github.com/nsoft/uno-jar/commit/cf5af42c447c22edb9bbc6bd08293f0c23db86c2之前肯定无法正常工作

也:最近提交了经过严格测试的代码警告:)

披露:我同时维护JesterJ和Uno-Jar(jurez提供的链接中提供的库One-JAR的分支),并欢迎任何错误报告或评论甚至贡献!!