Как загрузить файл jar во время выполнения

Мне было предложено создать java-систему, которая будет иметь возможность загружать новый код (расширения) во время работы. Как перезагрузить файл jar во время работы моего кода? или как загрузить новую банку?

Очевидно, что, поскольку постоянное время работы важно, я хотел бы добавить возможность повторно загружать существующие классы во время (если это не слишком усложняет).

Каковы вещи, на которые я должен обратить внимание? (подумайте об этом как о двух разных вопросах - о перезагрузке классов во время выполнения, а другой - о добавлении новых классов).

Ответ 1

Перезагрузка существующих классов с существующими данными, скорее всего, нарушит все.

Вы можете загружать новый код в новые загрузчики классов относительно легко:

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 и т.д.).

Если вы хотите сохранить данные объекта, тогда я предлагаю механизм сохранения, такой как сериализация или то, к чему вы привыкли.

Конечно, системы отладки могут делать более интересные вещи, но более хаки и менее надежны.

В классный загрузчик можно добавлять новые классы. Например, используя URLClassLoader.addURL. Однако, если класс не загружается (потому что, скажем, вы его не добавили), он никогда не будет загружаться в этом экземпляре загрузчика класса.

Ответ 2

Это работает для меня:

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

Ответ 3

Мне было предложено создать java-систему, которая будет иметь возможность загружать новый код во время работы

Возможно, вы захотите основать свою систему на OSGi (или, по крайней мере, взять на нее много), что было сделано для эта ситуация.

Беседа с загрузчиками классов - это действительно сложный бизнес, в основном из-за того, как работает видимость класса, и вы не хотите запускать сложные проблемы с отладкой позже. Например, Class.forName(), который широко используется во многих библиотеках, не слишком хорошо работает на фрагментированном пространстве загрузчика классов.

Ответ 4

Я немного искал Google и нашел этот код здесь:

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

Может ли кто-нибудь поделиться мнениями/комментариями/ответами относительно этого подхода?

Ответ 5

Используйте 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 manifest add it 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;
}