понимание urlclassloader, как получить доступ к загруженным классам jar

Я пытаюсь понять, как получить доступ/сделать доступным файл jar с помощью URLClassLoader.

Сначала я загружаю файл jar с помощью

    package myA;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

import org.jgroups.JChannel;



public class loader {

    JChannel channel;

        String user_name=System.getProperty("user.name", "n/a");


        private void start() throws Exception {

            channel=new JChannel(); // use the default config, udp.xml

            channel.connect("ChatCluster");

        }

    public void loadMe()throws ClassNotFoundException, MalformedURLException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
        URL classUrl;
        classUrl = new URL("file:///home/myJars/jgroups-3.4.2.Final.jar");
        URL[] classUrls = { classUrl };
        URLClassLoader ucl = new URLClassLoader(classUrls);
        Class<?> c = ucl.loadClass("org.jgroups.JChannel");
        for(Field f: c.getDeclaredFields()) {
            System.out.println("Field name=" + f.getName());
            }
        Object instance = c.newInstance();  
        //Method theMethod = c.getMethod("main");
        //theMethod.invoke(instance);
        }

     public static void main(String[] args) throws Exception {
         new loader().loadMe();
         new  loader().start();

     }
}

распечатка показывает объявленные поля, которые находятся в jgroups-3.4.2.Final.jar, однако затем выдает ошибку classnotfound.

java -cp myA.jar myA.loader
Field name=DEFAULT_PROTOCOL_STACK
Field name=local_addr
Field name=address_generator
Field name=name
Field name=cluster_name
Field name=my_view
Field name=prot_stack
Field name=state_promise
Field name=state_transfer_supported
Field name=flush_supported
Field name=config
Field name=stats
Field name=sent_msgs
Field name=received_msgs
Field name=sent_bytes
Field name=received_bytes
Field name=probe_handler
Exception in thread "main" java.lang.NoClassDefFoundError: org/jgroups/JChannel
        at myA.loader.start(loader.java:23)
        at myA.loader.main(loader.java:45)
Caused by: java.lang.ClassNotFoundException: org.jgroups.JChannel
        at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:425)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:358)
        ... 2 more

Не понимаю, почему в распечатке видно, что класс загружен, а потом не найден?

спасибо искусство


person art vanderlay    schedule 12.01.2014    source источник


Ответы (2)


В вашем коде может быть пара проблем. Во-первых, вы создаете экземпляр загрузчика 2 раза в main, поэтому второй экземпляр независим от первого и может не знать, что первый загрузил определение файла класса JChannel.

Кроме того, вы определили JChannel как член loader ранее, поэтому JRE должна требовать определения класса для него при запуске - иначе она не должна знать, каким должно быть это поле. Я заменил его классом, который вы загрузили через URLClassLoader, экземпляр которого вы должны создать в start().

package myA;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import org.jgroups.JChannel;

public class Loader 
{
    Class<?> clazz;
    String user_name=System.getProperty("user.name", "n/a");

    private void start() throws Exception 
    {
        if (this.clazz == null)
            throw new Exception("Channel class was not loaded properly");

        Object channel  = this.clazz.newInstance(); // use the default config, udp.xml
        Method chatCluster = this.clazz.getDeclaredMethod("connect", new Class[] { String.class });
        chatCluster.invoke(channel, "ChatCluster");
    }

    public void loadMe() throws Exception
    {
        URL classUrl;
        classUrl = new URL("file:///home/myJars/jgroups-3.4.2.Final.jar");
        URL[] classUrls = { classUrl };
        URLClassLoader ucl = new URLClassLoader(classUrls);
        Class<?> c = ucl.loadClass("org.jgroups.JChannel");
        for(Field f: c.getDeclaredFields()) 
        {
            System.out.println("Field name=" + f.getName());
        }
        this.clazz = c;
        Object instance = c.newInstance();  
        //Method theMethod = c.getMethod("main");
        //theMethod.invoke(instance);
     }

     public static void main(String[] args) throws Exception 
     {
         Loader loader = new Loader();
         loader.loadMe();
         loader.start();
     }
}

Вы должны дополнительно добавить в код некоторую обработку ошибок.

person Roman Vottner    schedule 12.01.2014
comment
@Vottner, спасибо за код и +1 за ясность, так как я изо всех сил пытался понять предыдущие комментарии. Могу ли я использовать ту же логику, чтобы сделать банку доступной перед вызовом класса Loader. т.е. если у меня есть parentA.class, который загружает Object channel = this.clazz.newInstance(), а затем вызывает Loader.class, будет ли он по-прежнему доступен для Loader?thx Art - person art vanderlay; 13.01.2014
comment
Я не совсем уверен, чего вы пытаетесь достичь, ТБХ. Вместо того, чтобы динамически загружать банку во время выполнения, вы можете загрузить ее во время запуска, указав банку в пути к классам команды java -cp (если ваш код находится в файле jar, вы даже можете добавить запись Class-Path: TheDependentJarToLoad.jar в MANIFEST.MF хостинга jar- файл, чтобы вы могли использовать java -jar yourJarFile.jar). Здесь вы просто можете создавать новые экземпляры, используя new JChannel() вместо использования отражения и загрузчиков классов. - person Roman Vottner; 13.01.2014
comment
Уточните, пожалуйста, что вы имеете в виду под make the jar available before calling the Loader class. Рефакторинг метода start() для родительского класса не имеет особого смысла, поскольку метод содержит дополнительный вызов метода connect("ChatCluster");, который может быть доступен только для класса JChannel. Вы можете преобразовать loadMe() в (абстрактный) родитель и добавить параметр для метода, который определяет имя загружаемого класса и сохраняет его в поле protected Class clazz;, которое вы можете использовать в дочернем элементе для создания экземпляра объекта требуемого класса. - person Roman Vottner; 13.01.2014

Exception in thread "main" java.lang.NoClassDefFoundError: org/jgroups/JChannel
    at myA.loader.start(loader.java:23)

Код не работает в этой строке:

       channel=new JChannel(); // use the default config, udp.xml

Тип JChannel невидим для загрузчика класса ClassLoader. Это будет очевидно, если вы попробуете:

       loader.class
             .getClassLoader()
             .loadClass("org.jgroups.JChannel");

У вас не должно быть никаких ссылок во время компиляции на зависимость, которая не будет находиться в пути к классам типа во время выполнения.

Загрузка с новым дочерним элементом ClassLoader не добавляет этот тип в какой-либо глобальный пул классов. Загрузчики иерархичны с дочерними и родительскими отношениями.

person McDowell    schedule 12.01.2014