如何在运行时加载一个jar文件

77

我被要求构建一个Java系统,能够在运行时加载新的代码(扩展)。如何在我的代码运行时重新加载JAR文件?或者如何加载新的JAR文件?

显然,由于持续运行时间很重要,如果不会过于复杂,我希望能够添加重新加载现有类的功能。

我应该注意哪些事项? (将其视为两个不同的问题 - 一个关于在运行时重新加载类,另一个关于添加新类)。


关于重新加载现有类,请考虑以下相关问题:- https://dev59.com/kHA75IYBdhLWcg3wlqET - http://blogs.oracle.com/CoreJavaTechTips/entry/closing_a_urlclassloader - André
5个回答

83

重新加载现有数据的现有类很可能会出现问题。

您可以相对容易地将新代码加载到新的类加载器中:

ClassLoader loader = URLClassLoader.newInstance(
    new URL[] { yourURL },
    getClass().getClassLoader()
);
Class<?> clazz = Class.forName("mypackage.MyClass", true, loader);
Class<? extends Runnable> runClass = clazz.asSubclass(Runnable.class);
// Avoid Class.newInstance, for it is evil.
Constructor<? extends Runnable> ctor = runClass.getConstructor();
Runnable doRun = ctor.newInstance();
doRun.run();

废弃的类加载器可以被垃圾回收(除非存在内存泄漏,比如使用ThreadLocal、JDBC驱动程序、java.beans等情况)。

如果您想保留对象数据,则建议使用诸如序列化之类的持久性机制,或者您习惯使用的其他机制。

当然,调试系统可以做更高级的事情,但是更加hacky和不太可靠。

可以将新类添加到类加载器中。例如,使用URLClassLoader.addURL。但是,如果一个类无法加载(比如说,您没有添加它),那么它就永远不会在该类加载器实例中加载。


如果我的初始类加载器加载了线程池和其他资源,那么第二个类加载器加载的新类是否意味着这些资源将无法访问新类? - Amir Arad
我没有为URLClassLoader指定父级,所以它将只使用系统(我会修复)。这意味着它仍然能够访问类路径上的类。如果您想共享内容,请使用通用类加载器中定义的类型传递引用(也许是Java lib的引导类加载器)。 - Tom Hawtin - tackline
2
我有点不明白:为什么Class.newInstance()是“邪恶”的? - Ryan Delucchi
4
Ryan,我已将此作为一个新问题发布 - 请参见:https://dev59.com/R3VC5IYBdhLWcg3wvT_g - Amir Arad

39

这对我有效:

File file  = new File("c:\\myjar.jar");

URL url = file.toURL();  
URL[] urls = new URL[]{url};

ClassLoader cl = new URLClassLoader(urls);
Class cls = cl.loadClass("com.mypackage.myclass");

11
根据Oracle的建议,使用.toURI().toURL()而不是使用.toURL() - Jesus Noland

8
我被要求构建一个具有运行时加载新代码能力的Java系统。
你可能希望基于OSGi(或至少查看它),因为它正是为这种情况而设计的。
玩弄类加载器真的很棘手,主要是因为类可见性的工作方式,你不想在以后遇到难以调试的问题。例如,许多库广泛使用的Class.forName()在碎片化的类加载器空间中效果不太好。

使用OSGi,您可以:1.在运行时替换JAR文件。2.当应用程序的不同部分依赖于不同版本时,保留相同JAR文件的多个版本。3.其他功能,如配置管理、服务工厂等等。 - Gadi

4

我谷歌了一下,找到了这段代码在这里:

File file = getJarFileToLoadFrom();   
String lcStr = getNameOfClassToLoad();   
URL jarfile = new URL("jar", "","file:" + file.getAbsolutePath()+"!/");    
URLClassLoader cl = URLClassLoader.newInstance(new URL[] {jarfile });   
Class loadedClass = cl.loadClass(lcStr);   

有人能分享关于这种方法的意见/评论/答案吗?


1
Jar URL 用于指向具有 jar 文件的资源。您想将整个 jar 文件传递给 URLClassLoader。(不幸的是,jar 中的 jar 是无法使用的。) - Tom Hawtin - tackline
修正了链接(或者说,我在谷歌上搜索“getJarFileToLoadFrom”,找到了另一个早于我的问题的代码副本:一种“最终一致性”方法来应对“互联网”)。 - Amir Arad

2
使用org.openide.util.Lookup和ClassLoader动态加载Jar插件,如下所示。
public LoadEngine() {
    Lookup ocrengineLookup;
    Collection<OCREngine> ocrengines;
    Template ocrengineTemplate;
    Result ocrengineResults;
    try {
        //ocrengineLookup = Lookup.getDefault(); this only load OCREngine in classpath of  application
        ocrengineLookup = Lookups.metaInfServices(getClassLoaderForExtraModule());//this load the OCREngine in the extra module as well
        ocrengineTemplate = new Template(OCREngine.class);
        ocrengineResults = ocrengineLookup.lookup(ocrengineTemplate); 
        ocrengines = ocrengineResults.allInstances();//all OCREngines must implement the defined interface in OCREngine. Reference to guideline of implement org.openide.util.Lookup for more information

    } catch (Exception ex) {
    }
}

public ClassLoader getClassLoaderForExtraModule() throws IOException {

    List<URL> urls = new ArrayList<URL>(5);
    //foreach( filepath: external file *.JAR) with each external file *.JAR, do as follows
    File jar = new File(filepath);
    JarFile jf = new JarFile(jar);
    urls.add(jar.toURI().toURL());
    Manifest mf = jf.getManifest(); // If the jar has a class-path in it's manifest add it's entries
    if (mf
            != null) {
        String cp =
                mf.getMainAttributes().getValue("class-path");
        if (cp
                != null) {
            for (String cpe : cp.split("\\s+")) {
                File lib =
                        new File(jar.getParentFile(), cpe);
                urls.add(lib.toURI().toURL());
            }
        }
    }
    ClassLoader cl = ClassLoader.getSystemClassLoader();
    if (urls.size() > 0) {
        cl = new URLClassLoader(urls.toArray(new URL[urls.size()]), ClassLoader.getSystemClassLoader());
    }
    return cl;
}

你喜欢复杂的东西。 - Elmue

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接