首页 文章

我应该如何在运行时动态加载Jars?

提问于
浏览
272

为什么在Java中这么做呢?如果您想拥有任何类型的模块系统,您需要能够动态加载jar . 我通过编写你自己的 ClassLoader 来做到这一点,但是对于那些应该(至少在我看来)就像调用一个带有jar文件作为参数的方法一样容易的东西来做很多工作 .

这样做的简单代码的任何建议?

15 回答

  • 1

    JCL class loader framework怎么样?我不得不承认,我没有使用它,但看起来很有希望 .

    用法示例:

    JarClassLoader jcl = new JarClassLoader();
    jcl.add("myjar.jar"); // Load jar file  
    jcl.add(new URL("http://myserver.com/myjar.jar")); // Load jar from a URL
    jcl.add(new FileInputStream("myotherjar.jar")); // Load jar file from stream
    jcl.add("myclassfolder/"); // Load class folder  
    jcl.add("myjarlib/"); // Recursively load all jar files in the folder/sub-folder(s)
    
    JclObjectFactory factory = JclObjectFactory.getInstance();
    // Create object of loaded class  
    Object obj = factory.create(jcl, "mypackage.MyClass");
    
  • 4

    安全的原因很难 . 类加载器意味着不可变;你不应该在运行时不断添加类 . 我真的很惊讶它与系统类加载器一起工作 . 以下是制作自己的子类加载器的方法:

    URLClassLoader child = new URLClassLoader(
            new URL[] {myJar.toURI().toURL()},
            this.getClass().getClassLoader()
    );
    Class classToLoad = Class.forName("com.MyClass", true, child);
    Method method = classToLoad.getDeclaredMethod("myMethod");
    Object instance = classToLoad.newInstance();
    Object result = method.invoke(instance);
    

    痛苦,但它确实存在 .

  • 3

    以下解决方案是hackish,因为它使用反射来绕过封装,但它完美无缺:

    File file = ...
    URL url = file.toURI().toURL();
    
    URLClassLoader classLoader = (URLClassLoader)ClassLoader.getSystemClassLoader();
    Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
    method.setAccessible(true);
    method.invoke(classLoader, url);
    
  • 3

    你应该看看OSGi,例如在Eclipse Platform实施 . 它正是如此 . 您可以安装,卸载,启动和停止所谓的捆绑包,这些捆绑包实际上是JAR文件 . 但它提供了更多,因为它提供了例如可以在运行时在JAR文件中动态发现的服务 .

    或者参见Java Module System的规范 .

  • 1

    这是一个未弃用的版本 . 我修改了原始文件以删除已弃用的功能 .

    /**************************************************************************************************
     * Copyright (c) 2004, Federal University of So Carlos                                           *
     *                                                                                                *
     * All rights reserved.                                                                           *
     *                                                                                                *
     * Redistribution and use in source and binary forms, with or without modification, are permitted *
     * provided that the following conditions are met:                                                *
     *                                                                                                *
     *     * Redistributions of source code must retain the above copyright notice, this list of      *
     *       conditions and the following disclaimer.                                                 *
     *     * Redistributions in binary form must reproduce the above copyright notice, this list of   *
     *     * conditions and the following disclaimer in the documentation and/or other materials      *
     *     * provided with the distribution.                                                          *
     *     * Neither the name of the Federal University of So Carlos nor the names of its            *
     *     * contributors may be used to endorse or promote products derived from this software       *
     *     * without specific prior written permission.                                               *
     *                                                                                                *
     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS                            *
     * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT                              *
     * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR                          *
     * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR                  *
     * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,                          *
     * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,                            *
     * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR                             *
     * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF                         *
     * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING                           *
     * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS                             *
     * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.                                   *
     **************************************************************************************************/
    /*
     * Created on Oct 6, 2004
     */
    package tools;
    
    import java.io.File;
    import java.io.IOException;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.net.URL;
    import java.net.URLClassLoader;
    
    /**
     * Useful class for dynamically changing the classpath, adding classes during runtime. 
     */
    public class ClasspathHacker {
        /**
         * Parameters of the method to add an URL to the System classes. 
         */
        private static final Class<?>[] parameters = new Class[]{URL.class};
    
        /**
         * Adds a file to the classpath.
         * @param s a String pointing to the file
         * @throws IOException
         */
        public static void addFile(String s) throws IOException {
            File f = new File(s);
            addFile(f);
        }
    
        /**
         * Adds a file to the classpath
         * @param f the file to be added
         * @throws IOException
         */
        public static void addFile(File f) throws IOException {
            addURL(f.toURI().toURL());
        }
    
        /**
         * Adds the content pointed by the URL to the classpath.
         * @param u the URL pointing to the content to be added
         * @throws IOException
         */
        public static void addURL(URL u) throws IOException {
            URLClassLoader sysloader = (URLClassLoader)ClassLoader.getSystemClassLoader();
            Class<?> sysclass = URLClassLoader.class;
            try {
                Method method = sysclass.getDeclaredMethod("addURL",parameters);
                method.setAccessible(true);
                method.invoke(sysloader,new Object[]{ u }); 
            } catch (Throwable t) {
                t.printStackTrace();
                throw new IOException("Error, could not add URL to system classloader");
            }        
        }
    
        public static void main(String args[]) throws IOException, SecurityException, ClassNotFoundException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException{
            addFile("C:\\dynamicloading.jar");
            Constructor<?> cs = ClassLoader.getSystemClassLoader().loadClass("test.DymamicLoadingTest").getConstructor(String.class);
            DymamicLoadingTest instance = (DymamicLoadingTest)cs.newInstance();
            instance.test();
        }
    }
    
  • 48

    使用 Java 9URLClassLoader 的答案现在给出如下错误:

    java.lang.ClassCastException: java.base/jdk.internal.loader.ClassLoaders$AppClassLoader cannot be cast to java.base/java.net.URLClassLoader
    

    这是因为使用的类加载器已经改变 . 相反,要添加到系统类加载器,可以通过代理使用Instrumentation API .

    Create an agent class:

    package ClassPathAgent;
    
    import java.io.IOException;
    import java.lang.instrument.Instrumentation;
    import java.util.jar.JarFile;
    
    public class ClassPathAgent {
        public static void agentmain(String args, Instrumentation instrumentation) throws IOException {
            instrumentation.appendToSystemClassLoaderSearch(new JarFile(args));
        }
    }
    

    Add META-INF/MANIFEST.MF and put it in a JAR file with the agent class:

    Manifest-Version: 1.0
    Agent-Class: ClassPathAgent.ClassPathAgent
    

    Run the agent:

    这使用byte-buddy-agent库将代理添加到正在运行的JVM:

    import java.io.File;
    
    import net.bytebuddy.agent.ByteBuddyAgent;
    
    public class ClassPathUtil {
        private static File AGENT_JAR = new File("/path/to/agent.jar");
    
        public static void addJarToClassPath(File jarFile) {
            ByteBuddyAgent.attach(AGENT_JAR, String.valueOf(ProcessHandle.current().pid()), jarFile.getPath());
        }
    }
    
  • 131

    我找到的最好的是org.apache.xbean.classloader.JarFileClassLoader,这是XBean项目的一部分 .

    这是我过去使用的一个简短方法,用于从特定目录中的所有lib文件创建类加载器

    public void initialize(String libDir) throws Exception {
        File dependencyDirectory = new File(libDir);
        File[] files = dependencyDirectory.listFiles();
        ArrayList<URL> urls = new ArrayList<URL>();
        for (int i = 0; i < files.length; i++) {
            if (files[i].getName().endsWith(".jar")) {
            urls.add(files[i].toURL());
            //urls.add(files[i].toURI().toURL());
            }
        }
        classLoader = new JarFileClassLoader("Scheduler CL" + System.currentTimeMillis(), 
            urls.toArray(new URL[urls.size()]), 
            GFClassLoader.class.getClassLoader());
    }
    

    然后使用类加载器,只需:

    classLoader.loadClass(name);
    
  • 214

    如果您正在使用Android,则以下代码有效:

    String jarFile = "path/to/jarfile.jar";
    DexClassLoader classLoader = new DexClassLoader(jarFile, "/data/data/" + context.getPackageName() + "/", null, getClass().getClassLoader());
    Class<?> myClass = classLoader.loadClass("MyClass");
    
  • 2

    jodonnell提出的解决方案很好,但应该稍微增强一点 . 我用这篇文章成功开发了我的应用程序 .

    分配当前线程

    首先我们要补充一下

    Thread.currentThread().setContextClassLoader(classLoader);
    

    或者你将无法加载存储在jar中的资源(例如spring / context.xml) .

    不包括

    你的jar进入父类加载器,否则你将无法理解谁正在加载什么 .

    另见Problem reloading a jar using URLClassLoader

    但是,OSGi框架仍然是最好的方法 .

  • 14

    Allain的另一个hackish解决方案版本,也适用于JDK 11:

    File file = ...
    URL url = file.toURI().toURL();
    URLClassLoader sysLoader = new URLClassLoader(new URL[0]);
    
    Method sysMethod = URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{URL.class});
    sysMethod.setAccessible(true);
    sysMethod.invoke(sysLoader, new Object[]{url});
    

    在JDK 11上,它提供了一些弃用警告,但作为在JDK 11上使用Allain解决方案的人的临时解决方案 .

  • 8

    使用Instrumentation的另一个工作解决方案适用于我 . 它具有修改类加载器搜索的优点,避免了依赖类的类可见性问题:

    Create an Agent Class

    对于此示例,它必须位于命令行调用的同一jar上:

    package agent;
    
    import java.io.IOException;
    import java.lang.instrument.Instrumentation;
    import java.util.jar.JarFile;
    
    public class Agent {
       public static Instrumentation instrumentation;
    
       public static void premain(String args, Instrumentation instrumentation) {
          Agent.instrumentation = instrumentation;
       }
    
       public static void agentmain(String args, Instrumentation instrumentation) {
          Agent.instrumentation = instrumentation;
       }
    
       public static void appendJarFile(JarFile file) throws IOException {
          if (instrumentation != null) {
             instrumentation.appendToSystemClassLoaderSearch(file);
          }
       }
    }
    

    Modify the MANIFEST.MF

    添加对代理的引用:

    Launcher-Agent-Class: agent.Agent
    Agent-Class: agent.Agent
    Premain-Class: agent.Agent
    

    我实际上使用Netbeans,所以this post有助于如何更改manifest.mf

    Running

    Launcher-Agent-Class 仅在JDK 9上受支持,并且负责加载代理而不在命令行上显式定义它:

    java -jar <your jar>
    

    适用于JDK 6的方法是定义 -javaagent 参数:

    java -javaagent:<your jar> -jar <your jar>
    

    Adding new Jar at Runtime

    然后,您可以使用以下命令根据需要添加jar:

    Agent.appendJarFile(new JarFile(<your file>));
    

    我在文档中没有发现任何问题 .

  • -2

    以下是Allain方法的快速解决方法,使其与更新版本的Java兼容:

    ClassLoader classLoader = ClassLoader.getSystemClassLoader();
    try {
        Method method = classLoader.getClass().getDeclaredMethod("addURL", URL.class);
        method.setAccessible(true);
        method.invoke(classLoader, new File(jarPath).toURI().toURL());
    } catch (NoSuchMethodException e) {
        Method method = classLoader.getClass()
                .getDeclaredMethod("appendToClassPathForInstrumentation", String.class);
        method.setAccessible(true);
        method.invoke(classLoader, jarPath);
    }
    

    请注意,它依赖于特定JVM内部实现的知识,因此它并不理想,并且它不是通用的解决方案 . 但如果您知道要使用标准OpenJDK或Oracle JVM,那么这是一种快速简便的解决方法 . 在将来发布新的JVM版本时,它可能会在某些时候中断,因此您需要牢记这一点 .

  • 40

    请看看我开始的这个项目:proxy-object lib

    此lib将从文件系统或任何其他位置加载jar . 它将为jar专用一个类加载器,以确保没有库冲突 . 用户将能够从加载的jar中创建任何对象并调用其上的任何方法 . 此lib旨在从支持Java 7的代码库加载在Java 8中编译的jar .

    要创建对象:

    File libDir = new File("path/to/jar");
    
        ProxyCallerInterface caller = ObjectBuilder.builder()
                .setClassName("net.proxy.lib.test.LibClass")
                .setArtifact(DirArtifact.builder()
                        .withClazz(ObjectBuilderTest.class)
                        .withVersionInfo(newVersionInfo(libDir))
                        .build())
                .build();
        String version = caller.call("getLibVersion").asString();
    

    ObjectBuilder支持工厂方法,调用静态函数和回调接口实现 . 我将在自述页面上发布更多示例 .

  • 21

    这可能是一个迟到的响应,我可以使用DataMelt的jhplot.Web类(这是一个简单的fastutil-8.2.2.jar示例)(http://jwork.org/dmelt

    import jhplot.Web;
    Web.load("http://central.maven.org/maven2/it/unimi/dsi/fastutil/8.2.2/fastutil-8.2.2.jar"); // now you can start using this library
    

    根据文档,此文件将在“lib / user”中下载,然后动态加载,因此您可以立即开始使用同一程序中此jar文件中的类 .

  • 6

    我个人觉得java.util.ServiceLoader做得很好 . 你可以得到一个例子here .

相关问题