problem z classloaderem

0

Mam w katalogu lib pliki bibl.jar i update.jar. Plik update.jar(zawierający nowe wersje klas z bibla) jest często uaktualniany podczas działania programu. I teraz pytanie: jak załadować podczas działania programu klasy z update.jar a nie a bibl.jar? Jakbym nie próbował, zawsze ładuje mi klasę z bibl.jar. Zależy mi na tym, aby oba pliki były w tym samym katalogu. Poniższe kawałki kodu pokazują co próbowałem zrobić

ClassLoader.getSystemClassLoader().loadClass("MojaKlasa");//pierwszy sposob
File plik = new File("lib\\update.jar");//drugi sposob
Class.forName("MojaKlasa",true,"new URLClassLoader(new URL[]{plik.toURI().toURL()}));
File plik = new File("lib\\update.jar");//trzeci sposob
new URLClassLoader(new URL[]{plik.toURI().toURL()}).loadClass("MojaKlasa");
0

Kazdy classloader ma parenta (z wyjatkiem tego najwyzszego, systtemowego). Teraz, jak tworzysz classloadera i nie podajesz parenta, to domyslnie jest to classloader ktory zaladowal kod obecnie wykonywany. Wgrywanie klas dziala tak ze najpierw robi sie odwolanie do parent classloadera, i jelsi on nie da rady wczytac klasy (lub nie wczytal jej wczesniej, i ma w cache) to dopiero aktualny classloader probuje.
W twoim przypadku URLClassloader daje szanse swojemu rodzicowi, ktory wczytuje klasy z classpath. Nastepnie, jak probujesz stworzyc nowy classloader z nowym jarem, tak czy tak wgranie klasy najpierw daje szanse rodzicowi (ponownie, ten co czyta z classpath), a tam juz jest klasa w cache. Tak nie da rady.
Mozesz zrobic np tak, ze tworzac swojego classloadera podajesz mu jako parenta null (czyli classloader systemowy), wtedy rodzic nie bedzie mial szansy wgrac tych klas poniewaz nie sa to klasy jdk. Albo, pominac te jary w classpath, i tworzyc URLClassloadery tak jak robisz to teraz. W ten sposob, proba wgrania klasy sprobuje sie odwolac do classloadera z classpath, a tam nie ma tych jarow, wiec wroci do twojego classloadera.
Uf, mam nadzieje ze pomoglem, troche zakrecony jestem.

0

tworzac swojego classloadera podajesz mu jako parenta null (czyli classloader systemowy)

Swojego classloadera czyli jego instancję, czy własną klasę dziedziczącą po classloaderze ?

pominac te jary w classpath, i tworzyc URLClassloadery tak jak robisz to teraz

co do classpath - zrobiłem w build.xml takie coś:

<path id="project.cp">
	<fileset dir="${libDir}">
		<include name="**/*.jar"/>
                <exclude name="**/update.jar">
	</fileset>
</path>

a potem

<javac (...) classpathref="project.cp"/>

i w takim przypadku program się wiesza, jeżeli zastosuję URLClassLoader.loadClass(), albo dalej wczytuje starą klasę, jeżeli użyję

Class.forName("MojaKlasa",true,new URLClassLoader(new URL[]{new File(sciezka_wzgledna_lub_bezwzgledna).toURI().toURL()}))

Wiesza się także, jak próbuję zrobić new URLClassLoader(new URL[]{new File(sciezka).toURI().toURL()},null);

0

edit: wiesza się jedynie w przypadku, jeżeli podaję do konstruktora URLClassLoader parenta=null, w innych przypadkach ładuje starą wersję klasy

0

Jak wroce z pracy do domu to podesle ci linka, napisalem takie male cos wczoraj ktore pozwala to przetestowac i dziala. Z podawaniem null jako parenta.
Pozdro.

0

Też napisałem przykładowy program z czterema różnymi loaderami. Użytkownik wybiera którego użyć. Dwa loadery odczytują klasę z jarów, dwa z podkatalogów w których klasy leżą luzem. Działa. Kod ma 150 wierszy, wstawić na forum czy umieścić linka do programu ?

0

Dzięki za zainteresowanie. Dla mnie nie ma znaczenia, w jakiej formie kod będzie dostępny, chociaż dla przyszłego użytku może lepiej wkleić go na forum, bo nie każdemu będzie się chciało ściągać źródła.

0

Organizacja plików jest taka: W pewnym katalogu winny być pliki Loaders.class, T.class, pl1.jar i p2.jar (jary zawierają klasę Test), katalog główny ma dwa podkatalogi, każdy z nich zawiera klasę Test.
Klasa Test

public class Test implements T
{
    public String getOpis()
    {
        return "Klasa 1"; // w różnych klasach Test tekst winien być inny aby widzieć, która klasa jest 
                                 // ładowana
    }
}

Interfejs T

public interface T
{
    public String getOpis();
}

Klasa testująca Loaders

import java.awt.event.*;
import java.awt.*;
import javax.swing.*;
import java.net.*;
import java.io.*;

public class Loaders extends JFrame implements ActionListener
{
    private JTextField tf=new JTextField(20);
    private ClassLoader loader1=null;
    private ClassLoader loader2=null;
    private ClassLoader loader3=null;
    private ClassLoader loader4=null;
    private Class klasa;
    //------------------------
    public static void main(String[] args)
    {
        new Loaders();
    }
    //------------------------
    public Loaders()
    {
        super();
        File f=new File("kat1");
        URL[] urls=new URL[1];
        try
        {
            urls[0]=f.toURI().toURL();
            System.out.println(urls[0]);
            loader1=new URLClassLoader(urls);
        }
        catch(Exception e)
        {
            System.out.println(e);
        }
        f=new File("kat2");
        try
        {
            urls[0]=f.toURI().toURL();
            System.out.println(urls[0]);
            loader2=new URLClassLoader(urls);
        }
        catch(Exception e)
        {
            System.out.println(e);
        }
        f=new File("pl1.jar");
        urls=new URL[1];
        try
        {
            urls[0]=f.toURI().toURL();
            System.out.println(urls[0]);
            loader3=new URLClassLoader(urls);
        }
        catch(Exception e)
        {
            System.out.println(e);
        }
        f=new File("pl2.jar");
        try
        {
            urls[0]=f.toURI().toURL();
            System.out.println(urls[0]);
            loader4=new URLClassLoader(urls);
        }
        catch(Exception e)
        {
            System.out.println(e);
        }

        setLayout(new FlowLayout(FlowLayout.CENTER));
        JButton b=new JButton("Katalog kat1");
        b.addActionListener(this);
        add(b);
        
        b=new JButton("Katalog kat2");
        b.addActionListener(this);
        add(b);

        tf.setEditable(false);
        add(tf);

        b=new JButton("Jar pl1.jar");
        b.addActionListener(this);
        add(b);
        
        b=new JButton("Jar pl2.jar");
        b.addActionListener(this);
        add(b);

        pack();
        setLocationRelativeTo(null);
       	setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setVisible(true);

    }
    //------------------------
    public void actionPerformed(ActionEvent ae)
    {
        if(ae.getActionCommand().equals("Katalog kat1"))
        {
            if(loader1!=null)
            {
                try
                {
                    klasa=loader1.loadClass("Test");
                }
                catch(Exception e)
                {
                    System.out.println(e);
                }
            }
        }
        if(ae.getActionCommand().equals("Katalog kat2"))
        {
            if(loader2!=null)
            {
                try
                {
                    klasa=loader2.loadClass("Test");
                }
                catch(Exception e)
                {
                    System.out.println(e);
                }
            }
        }
        if(ae.getActionCommand().equals("Jar pl1.jar"))
        {
            if(loader3!=null)
            {
                try
                {
                    klasa=loader3.loadClass("Test");
                }
                catch(Exception e)
                {
                    System.out.println(e);
                }
            }
        }
        if(ae.getActionCommand().equals("Jar pl2.jar"))
        {
            if(loader4!=null)
            {
                try
                {
                    klasa=loader4.loadClass("Test");
                }
                catch(Exception e)
                {
                    System.out.println(e);
                }
            }
        }
        if(klasa==null)
        {
            return;
        }
        try
        {
            T t=(T)klasa.newInstance();
            tf.setText(t.getOpis());
        }
        catch(Exception e)
        {
            System.out.println(e);
        }
    }
}
0

Niestety klasy które mam updateować nie są interfejsami. Widziałem już gdzieś podobne rozwiązanie, ale odrzuciłem je ze względu właśnie na interfejsy. Niemniej dzięki, wypróbuję to, jednak wolałbym wersję, gdzie można updateować dowolne klasy.

0

Wydaje mi się, że bez interfejsów jest problem tu:

  PewnaKlasa t=(PewnaKlasa)klasa.newInstance();

która wersja klasy PewnaKlasa zostanie użyta podczas rzutowania ?
Używana klasa nie jest interfejsem, interfejs jest wykorzystywany tylko do rzutowania.
Jak nie korzystałem z interfesu i rzutowałem tak

   Test t=(Test)klasa.newInstance();

to jeśli klasy Test nie było w katalogu programu, to był błąd wykonania. Jeśli była, to niezależnie od tego co wczytał loader rzutowało się na klasę z katalogu programu. Dlatego wymyśliłem interfejs.

0

Moj kod wyglada podobnie:

package test;

import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;

class DynamicClasses {

    public static void main(String args[]) throws Exception {
        JFrame frame = new JFrame("Dynamic classes test");
        frame.setLayout(new FlowLayout());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JButton button = new JButton("Do it");
        button.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                try {
                    JOptionPane.showMessageDialog(null, doIt());
                } catch (Exception exc) {
                    exc.printStackTrace();
                }
            }

        });
        frame.add(button);
        frame.pack();
        frame.setVisible(true);
    }

    private static String doIt() throws Exception {
        ClassLoader cl = new URLClassLoader(new URL[] { new File("/home/rafal/dynamicclasses").toURI().toURL() }); //$NON-NLS-1$
        Class<?> klass = cl.loadClass("test.Klass");
        Doable doable = (Doable) klass.newInstance();
        return doable.doIt();
    }
}
package test;

public interface Doable {

    String doIt();
}
package test;

public class Klass implements Doable {

    @Override
    public String doIt() {
        return "Klass One";
    }

}
package test;

public class Klass implements Doable {

    @Override
    public String doIt() {
        return "Klass Two";
    }

}

Zakladam ze:

  1. CLASSPATH wskazuje na jara / katalog z klasa test.DynamicClasses oraz test.Doable, nie widzi klasy test.Klass
  2. katalog /home/malamyga/dynamicclasses ma implementacje klasy test.Klass ktora zwraca string "Klass One"
  3. uruchamiasz aplikacje, klikasz guzik, i masz okienko ktore wypisze "Klass One"
  4. kopiujesz w miejsce /home/malamyga/dynamicclasses/test/Klass.class klase ktora zwraca "Klass Two"
  5. klikasz guzik i masz okienko z napisem "Klass Two"

Ja rowniez uzylem interfejsu, ktory jest dostepny dla aplikacji, poniewaz zalozylem ze te dynamicznie ladowane klasy maja byc takimi jakby pluginami, ktore mozna podmieniac. Poza tym, chcesz w koncu wiedziec jakie metody wywolac, prawda? Jesil bys nie rzutowal na interfejs, to przypiszesz co do referencji na Object, i moglbys wywolywac metody co najwyzej za pomoca refleksji, a to jest wolniejsze, troche trzeba napisac kodu, i nie ma sensu w tym krotkim przykladzie (aczkolwiek, moze miec sens w Twoim, wiec jesli tego wymaga ta aplikacja to zrob tak). Chodzi o to zeby zaprezentowac jak zrobic takie dynamiczne ladowanie klas. Oczywiscie, jesli twoje podmieniane klasy nie maja ze soba nic wspolnego, to tak czy tak ladowanie dynamiczne bedzie dzialac, nie bedzie tylko zadnego wspolnego parenta zeby wywolac jakas ciekawa metode i przetestowac (chyba ze przeciazysz cos z Object).

P.S. Nie wczytywalem sie w kod Bogdansa, mozliwe ze jest niemalze identyczny, jelsi tak to sorki za powtarzanie.

0

@malamyga, istota jest chyba taka sama. Ja bardziej rozbudowałem część pokazującą że działa. Są cztery loadery: dwa ładują z różnych jarów, dwa ładują z podkatalogów. Użytkownik decyduje z którego loadera korzysta.

0

@bogdans
Cool, wiec obaj proponujemy takie samo rozwiazanie czyli jest w porzadku i powinno dzialac ;-)
Pozdrawiam.

0

Swego czasu napisałem parę programów, które wykorzystywały tę technikę więc chyba ona działa. Jeden z nich rysował wykres funkcji, lista dostępnych funkcji była zapisana w pliku XML, który zawierał między innymi nazwy klas wyliczających wartości funkcji. Na etapie kompilacji programu nazwy te nie były więc znane. Ponadto jeżeli program był uruchamiany na komputerze z kompilatorem, to użytkownik mógł dodawać własne funkcje.
Pozdrawiam
P.S. Też miałem wrażenie, że jeśli dwie (właściwe) głowy wymyślą to samo rozwiązanie, to jest bardzo dobre rozwiązanie.

0

@bogdans
Nie wspomniałeś, czy obie wersje klasy muszą być niewidoczne dla CLASSPATH. A jeśli tak, to jak w takim razie uwzględnić to, że w pierwszym jarze oprócz tej klasy są jeszcze inne, niezbędne do kompilacji?

I jeszcze jedno:

public class JeszczeJednaKlasa {
     public void doSomething(){
          Klass klas = new Klass();
          (...)
     }
}

Mam podobną sytuację do powyższej, przy czym nie mogę (nie powinieniem) zmieniać tej implementacji, a JeszczeJednaKlasa siedzi w tym samym jarze co stara wersja Klass. Czy w takim przypadku przeładowanie Klass przed inicjalizacją JeszczeJednaKlasa wystarczy?

0

Nie znam (mam nadzieję że chwilowo) odpowiedzi na ostatnie pytania. Przyszło mi do głowy takie rozwiązanie twojego problemu: w zmiennej CLASSPATH plik update.jar występuje przed plikiem bibl.jar.
Dokumentacja potwierdzona eksperymentami stwierdza, że wpierw działa loader wbudowany w JVM a on korzysta ze zmiennej CLASSPATH. Zatem jedynym (chyba) lekarstwem na twój problem jest właściwa kolejność katalogów w
zmiennej CLASSPATH, najpierw update.jar potembibl.jar.
pozdrawiam

0

Z tego co piszesz wynika ze chcesz miec jednoczesnie Klass ladowane poprzez CLASSPATH (twoj przykladowy kod), oraz dynamicznie. Jakos mi to nie pasuje, nie wiem dlaczego tak chcesz robic. Mozesz zaladowac inna wersje Klass tworzac URLClassLoader z nullowym parentem, ale nie bedzie ona kompatybilna z ta ladowana z classpath (nawet jesli jest to bajt w bajt to samo) - dla testu sprobuj wywolac equals() na takich obiektach Class dla tych dwoch klas (wiem, zle to brzmi) - inne classloadery, czyli inne klasy, nie mozesz rzutowac ani przypisac. Zatem, utworzenie takiego obiektu wczytanej dynamicznie klasy, rzutowanie go na Klass (ktora jest z classpath) rzuci ClassCastException (z wiele mowiaca wiadomoscia bledu "Exception in thread "main" java.lang.ClassCastException: test.Klass cannot be cast to test.Klass") - bo inne classloadery je wczytaly, czyli wg javy klasy sa niekompatybilne.
Kod ktory wykonalem zeby rzucic taki wyjatek:

Klass k1 = new Klass(); // wersja z classpath
        Klass k2 = (Klass) new URLClassLoader(new URL[] { new File("/home/malamyga/dynamicclasses").toURI().toURL() }, null).loadClass("test.Klass").newInstance(); // wczytana dynamiczniem ale rzutowana na wersje z classpath - wyjatek

Obiekt class ma referencje do classloadera ktory ja zaladowal, a classloader ma referencje do klas ktore byly nim zaladowane, taki cache. Wynika zatem z tego, ze aby "odladowac" klase Klass wczytana z classpath (i zastapic ja inna implementacja) musialbys puscic referencje do tego classloadera, oraz wszystkich klas ktore nim zostaly wczytane - tylko wtedy GC sciagnie smieciory. Nie jestem pewien, ryzykuje stwierdzenie ze nie jest to wykonalne, poniewaz tego classloadera nie tworzysz ty sam, a i nie odladujesz klasy glownej programu, wczytanej wlasnie nim. Dzialajac tak jak zostalo to pokazane we wczesniejszych postach, tracac referencje do URLClassloaderow i obiektow klas nim wczytanych, "odladowujesz" te klasy z pamieci, i mozesz zaladowac inne.
Reasumujac, zdecyduj czy ladujesz dynamicznie czy nie, z classpath, inaczej bedziesz mial ciagle jakies klopoty i dziwne jazdy. A jesli chcesz moc wywolywac metody na jakiejkimkolwiek obiekcie Klass, utworz interfejs i nim sie posluguj (umiesc go w classpath), przypisujac do niego klasy wczytane i utworzone dynamicznie, jak we wczesniejszych przykladach.
Jeszcze jedno - jesli chcesz miec 2 definicje Klass w classpath, to zawsze bedzie brana ta pierwsza z pierwszego jara wyszczegolnionego w classpath wlasnie - no i nigdy nie zostanie wczytana ta druga wersja. Ponadto, jesli raz wczytasz klase, i nastepnie nadpiszesz jara ja zawierajacego, to i tak nie bedzie on czytany (przynajmniej nie gdy wczytana ma byc nowa wersja Klass, jesli chcesz uzyc klasy jeszcze nie wykorzystane wczesniej to oczywiscie jar bedzie dla nich czytany) poniewaz Klass jest wczytana i jest w cache classloadera classpath.
Ma to sens?

0

Problem został rozwiązany - klasy do przeładowania miały być wyłączone z głównego jara, a nie tylko dodatkowo wrzucane do innego, nie do końca zrozumiałem zadanie. Sorry za niepotrzebny kłopot i dzięki za pomoc.

1 użytkowników online, w tym zalogowanych: 0, gości: 1