How to load a jar file at runtime [duplicate]
Asked Answered
S

5

77

I was asked to build a java system that will have the ability to load new code (expansions) while running. How do I re-load a jar file while my code is running? or how do I load a new jar?

Obviously, since constant up-time is important, I'd like to add the ability to re-load existing classes while at it (if it does not complicate things too much).

What are the things I should look out for? (think of it as two different questions - one regarding reloading classes at runtime, the other regarding adding new classes).

Sheffield answered 11/10, 2008 at 21:42 Comment(1)
In regard to reloading existing classes, consider this related issue: - #3217280 - blogs.oracle.com/CoreJavaTechTips/entry/…Seringapatam
J
83

Reloading existing classes with existing data is likely to break things.

You can load new code into new class loaders relatively easily:

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();

Class loaders no longer used can be garbage collected (unless there is a memory leak, as is often the case with using ThreadLocal, JDBC drivers, java.beans, etc).

If you want to keep the object data, then I suggest a persistence mechanism such as Serialisation, or whatever you are used to.

Of course debugging systems can do fancier things, but are more hacky and less reliable.

It is possible to add new classes into a class loader. For instance, using URLClassLoader.addURL. However, if a class fails to load (because, say, you haven't added it), then it will never load in that class loader instance.

Jadajadd answered 11/10, 2008 at 21:57 Comment(4)
if my initial classloader loaded stuff like threadpools and other resources, does it not mean that the new classes loaded by the second classloader will not be accessible to those resources?Sheffield
I've not specified a parent for the URLClassLoader so it'll just use system (I'll fix). That will mean it'll still be able to access classes on the classpath. If you want to share stuff, pass references around using types defined in common class loaders (perhaps the boot class loader for Java lib).Jadajadd
I'm missing something: why is Class.newInstance() "evil"?Maximilian
Ryan, I posted this as a new question - see: https://mcmap.net/q/57563/-why-is-class-newinstance-quot-evil-quotSheffield
I
39

This works for me:

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");
Ireful answered 23/3, 2009 at 13:44 Comment(1)
According to Oracle instead of using .toURL() use .toURI().toURL().Doyen
D
8

I was asked to build a java system that will have the ability to load new code while running

You might want to base your system on OSGi (or at least take a lot at it), which was made for exactly this situation.

Messing with classloaders is really tricky business, mostly because of how class visibility works, and you do not want to run into hard-to-debug problems later on. For example, Class.forName(), which is widely used in many libraries does not work too well on a fragmented classloader space.

Dope answered 11/1, 2009 at 10:49 Comment(1)
with OSGi you can 1. replace jars in runtime. 2. keep several versions of the same jar when different parts of your app depending on different versions. 3. other features like configuration management, service factories and many more.Curia
S
4

I googled a bit, and found this code here:

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);   

Can anyone share opinions/comments/answers regarding this approach?

Sheffield answered 11/10, 2008 at 21:55 Comment(2)
The jar URL is for pointing at a resource with a jar file. You want to pass the whole jar file over to URLClassLoader. (Unfortunately jars within jars is broken.)Jadajadd
fixed link (or rather, googled "getJarFileToLoadFrom" and found another copy of the code that predates my question: an "eventually consistent" approach to "internet")Sheffield
T
2

Use org.openide.util.Lookup and ClassLoader to dynamically load the Jar plugin, as shown here.

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;
}
Tableware answered 23/5, 2012 at 7:9 Comment(1)
You like it complicated.Propound

© 2022 - 2024 — McMap. All rights reserved.