Java, Classpath, Classloading => Wiele wersji tego samego pliku jar / projektu

118

Wiem, że to może być głupie pytanie dla doświadczonych programistów. Ale mam bibliotekę (klienta http), której wymagają niektóre inne frameworki / pliki JAR używane w moim projekcie. Ale wszystkie wymagają różnych głównych wersji, takich jak:

httpclient-v1.jar => Required by cralwer.jar
httpclient-v2.jar => Required by restapi.jar
httpclient-v3.jar => required by foobar.jar

Czy Classloader jest wystarczająco inteligentny, aby je jakoś rozdzielić? Najprawdopodobniej nie? W jaki sposób Classloader radzi sobie z tym w przypadku, gdy klasa jest taka sama we wszystkich trzech słoikach. Który jest załadowany i dlaczego?

Czy Classloader pobiera tylko dokładnie jeden słoik, czy też dowolnie miesza klasy? Na przykład, jeśli klasa jest ładowana z pliku Version-1.jar, wszystkie inne klasy ładowane z tego samego modułu ładującego klasy trafią do tego samego pliku jar?

Jak radzisz sobie z tym problemem?

Czy jest jakaś sztuczka, aby w jakiś sposób „włączyć” słoiki do pliku „required.jar”, ​​tak aby były one postrzegane jako „jedna jednostka / opakowanie” Classloaderlub w jakiś sposób połączone?

jens
źródło

Odpowiedzi:

57

Problemy związane z Classloaderem to dość złożona sprawa. W każdym przypadku należy pamiętać o kilku faktach:

  • Programy ładujące klasy w aplikacji to zwykle więcej niż jeden. Program ładujący klasy bootstrap deleguje do odpowiedniego pliku. Podczas tworzenia wystąpienia nowej klasy wywoływany jest bardziej szczegółowy program ładujący klasy. Jeśli nie znajdzie odniesienia do klasy, którą próbujesz załadować, deleguje do swojej klasy nadrzędnej i tak dalej, aż dojdziesz do programu ładującego klasy ładowania początkowego. Jeśli żaden z nich nie znajdzie odniesienia do klasy, którą próbujesz załadować, otrzymasz ClassNotFoundException.

  • Jeśli masz dwie klasy o tej samej nazwie binarnej, które można przeszukiwać przez ten sam program ładujący klasy, i chcesz wiedzieć, którą z nich ładujesz, możesz tylko sprawdzić, w jaki sposób określony program ładujący klasy próbuje rozwiązać nazwę klasy.

  • Zgodnie ze specyfikacją języka java nie ma ograniczenia co do niepowtarzalności nazwy binarnej klasy, ale z tego, co widzę, powinna być unikalna dla każdego modułu ładującego klasy.

Potrafię wymyślić sposób na załadowanie dwóch klas o tej samej nazwie binarnej i wymaga ich załadowania (i wszystkich ich zależności) przez dwa różne programy ładujące klasy, nadpisujące domyślne zachowanie. Surowy przykład:

    ClassLoader loaderA = new MyClassLoader(libPathOne);
    ClassLoader loaderB = new MyClassLoader(libPathTwo);
    Object1 obj1 = loaderA.loadClass("first.class.binary.name", true)
    Object2 obj2 = loaderB.loadClass("second.class.binary.name", true);

Zawsze uważałem, że dostosowywanie classloadera jest trudne. Raczej sugerowałbym unikanie wielu niezgodnych zależności, jeśli to możliwe.

Luca Putzu
źródło
13
Program ładujący klasy bootstrap deleguje do odpowiedniego pliku. Podczas tworzenia wystąpienia nowej klasy wywoływany jest bardziej szczegółowy program ładujący klasy. Jeśli nie znajdzie odniesienia do klasy, którą próbujesz załadować, deleguje ją do swojej klasy nadrzędnej. Proszę o wyrozumiałość, ale zależy to od strategii modułu ładującego klasy, która jest domyślnie Parent First. Innymi słowy, klasa potomna najpierw poprosi swojego rodzica o załadowanie klasy i załaduje się tylko wtedy, gdy cała hierarchia jej nie załaduje, nie ??
deckingraj
5
Nie - zwykle moduł ładujący klasy deleguje do swojego elementu nadrzędnego przed wyszukaniem samej klasy. Zobacz klasę javadoc dla Classloader.
Joe Kearney
1
Myślę, że kocur robi to w sposób opisany tutaj, ale "konwencjonalna" delegacja polega najpierw na zapytaniu rodzica
rogerdpack
@deckingraj: po pewnym googlowaniu znalazłem to w oracle docs: „W projekcie delegowania program ładujący klasy deleguje ładowanie klasy do swojego elementu nadrzędnego przed próbą załadowania samej klasy. [...] Jeśli moduł ładujący klasy nadrzędnej nie może załadować klasy, program ładujący klasy próbuje załadować samą klasę. W efekcie program ładujący klasy jest odpowiedzialny za załadowanie tylko klas niedostępnych dla elementu nadrzędnego ". Zbadam dalej. Jeśli pojawi się jako domyślna implementacja, odpowiednio zaktualizuję odpowiedź. ( docs.oracle.com/cd/E19501-01/819-3659/beadf/index.html )
Luca Putzu,
20

Każde ładowanie klas wybiera dokładnie jedną klasę. Zwykle pierwszy znaleziony.

OSGi ma na celu rozwiązanie problemu wielu wersji tego samego słoika. Equinox i Apache Felix to typowe implementacje Open Source dla OSGi.

Tarlog
źródło
6

Classloader załaduje klasy z pliku jar, który przypadkowo znalazł się jako pierwszy w ścieżce klas. Zwykle niekompatybilne wersje biblioteki będą miały różnice w pakietach, ale w mało prawdopodobnym przypadku są one naprawdę niekompatybilne i nie można ich zastąpić jednym - spróbuj jarjar.

Alex Gitelman
źródło
6

Classloadery ładują klasę na żądanie. Oznacza to, że klasa wymagana najpierw przez aplikację i powiązane biblioteki zostanie załadowana przed innymi klasami; żądanie załadowania klas zależnych jest zwykle wysyłane podczas procesu ładowania i łączenia klasy zależnej.

Prawdopodobnie napotkasz komunikat LinkageErrorstwierdzający, że napotkano zduplikowane definicje klas dla programów ładujących klasy, zazwyczaj nie podejmuje się próby określenia, która klasa powinna zostać załadowana jako pierwsza (jeśli istnieją dwie lub więcej klas o tej samej nazwie obecne w ścieżce klas modułu ładującego). Czasami program ładujący klasy ładuje pierwszą klasę występującą w ścieżce klas i ignoruje zduplikowane klasy, ale zależy to od implementacji modułu ładującego.

Zalecaną praktyką rozwiązywania tego rodzaju błędów jest użycie osobnego modułu ładującego klasy dla każdego zestawu bibliotek, które mają sprzeczne zależności. W ten sposób, jeśli program ładujący klasy spróbuje załadować klasy z biblioteki, klasy zależne zostaną załadowane przez ten sam program ładujący klasy, który nie ma dostępu do innych bibliotek i zależności.

Vineet Reynolds
źródło
1

Możesz użyć URLClassLoaderfor require, aby załadować klasy z wersji jar diff-2:

URLClassLoader loader1 = new URLClassLoader(new URL[] {new File("httpclient-v1.jar").toURL()}, Thread.currentThread().getContextClassLoader());
URLClassLoader loader2 = new URLClassLoader(new URL[] {new File("httpclient-v2.jar").toURL()}, Thread.currentThread().getContextClassLoader());

Class<?> c1 = loader1.loadClass("com.abc.Hello");

Class<?> c2 = loader2.loadClass("com.abc.Hello");

BaseInterface i1 = (BaseInterface) c1.newInstance();

BaseInterface i2 = (BaseInterface) c2.newInstance();
Pankaj Kalra
źródło