Różnica między modułem ładującym klasy kontekstu wątku a normalnym modułem ładującym

242

Jaka jest różnica między programem ładującym klasy kontekstu wątku a normalnym programem ładującym klasy?

To znaczy, jeśli Thread.currentThread().getContextClassLoader()i getClass().getClassLoader()zwrócą różne obiekty ładujące klasy, który z nich zostanie użyty?

abrakadabra
źródło

Odpowiedzi:

151

Każda klasa użyje własnego modułu ładującego klasy, aby załadować inne klasy. Więc jeśli ClassA.classreferencje ClassB.classnastępnie ClassBmusi być na ścieżce klasy classloader z ClassA, lub jego rodziców.

Moduł ładujący klasy kontekstu wątku jest bieżącym modułem ładującym klasy dla bieżącego wątku. Obiekt można utworzyć z klasy w, ClassLoaderCa następnie przekazać do wątku należącego do ClassLoaderD. W takim przypadku obiekt musi użyć Thread.currentThread().getContextClassLoader()bezpośrednio, jeśli chce załadować zasoby, które nie są dostępne w jego własnym module ładującym.

David Roussel
źródło
1
Dlaczego mówicie, że ClassBmusi to być ścieżka klasy programu ClassAładującego (lub ClassArodziców programu ładującego)? Czy nie jest możliwe, aby ClassAmoduł ładujący nadpisał loadClass(), tak że można go pomyślnie załadować, ClassBnawet jeśli ClassBnie ma ścieżki klas?
Pacerier
8
Rzeczywiście, nie wszystkie moduły ładujące klasy mają ścieżkę klas. Kiedy napisałem „Klasa B musi znajdować się na ścieżce klas modułu ładującego klasy A”, miałem na myśli „Klasa B musi być ładowana przez moduł ładujący klasy A”. 90% czasu oznaczają to samo. Ale jeśli nie używasz modułu ładującego klasy opartego na adresie URL, to tylko drugi przypadek jest prawdziwy.
David Roussel
Co to znaczy powiedzieć, że „ ClassA.classreferencje ClassB.class”?
jameshfisher
1
Gdy ClassA ma instrukcję importu dla ClassB, lub jeśli istnieje metoda w ClassA, która ma zmienną lokalną typu ClassB. Spowoduje to załadowanie ClassB, jeśli nie jest jeszcze załadowane.
David Roussel
Myślę, że mój problem jest związany z tym tematem. Co sądzisz o moim rozwiązaniu? Wiem, że to nie jest dobry wzór, ale nie mam innego pomysłu, jak to naprawić: stackoverflow.com/questions/29238493/…
Marcin Erbel
109

To nie odpowiada na pierwotne pytanie, ale ponieważ pytanie jest wysoko ocenione i powiązane z każdym ContextClassLoaderzapytaniem, myślę, że ważne jest, aby odpowiedzieć na powiązane pytanie, kiedy należy użyć modułu ładującego klasy kontekstu. Krótka odpowiedź: nigdy nie używaj modułu ładującego klasy kontekstu ! Ale ustaw tę getClass().getClassLoader()opcję, gdy trzeba wywołać metodę, w której brakuje ClassLoaderparametru.

Gdy kod z jednej klasy prosi o załadowanie innej klasy, właściwym modułem ładującym jest ten sam moduł ładujący co klasa wywołująca (tj getClass().getClassLoader().). W ten sposób działa 99,9% czasu, ponieważ JVM robi to samo przy pierwszym utworzeniu instancji nowej klasy, wywołaniu metody statycznej lub uzyskaniu dostępu do pola statycznego.

Jeśli chcesz utworzyć klasę za pomocą odbicia (na przykład podczas deserializacji lub ładowania konfigurowalnej nazwanej klasy), biblioteka wykonująca odbicie powinna zawsze pytać aplikację, który moduł ładujący klasy ma użyć, otrzymując ClassLoaderjako parametr od aplikacji. Aplikacja (która zna wszystkie klasy wymagające budowy) powinna ją przekazać getClass().getClassLoader().

Każdy inny sposób uzyskania modułu ładującego klasy jest niepoprawny. Jeśli biblioteka używa hacków takich jak Thread.getContextClassLoader(), sun.misc.VM.latestUserDefinedLoader()lub sun.reflect.Reflection.getCallerClass()jest to błąd spowodowany brakiem interfejsu API. Zasadniczo Thread.getContextClassLoader()istnieje tylko dlatego, że ktokolwiek zaprojektował ObjectInputStreamAPI, zapomniał zaakceptować ten ClassLoaderparametr, a ten błąd nawiedził społeczność Java do dziś.

To powiedziawszy, wiele wielu klas JDK używa jednego z kilku hacków, aby odgadnąć jakiś moduł ładujący klasy. Niektóre używają ContextClassLoader(co kończy się niepowodzeniem, gdy uruchamiasz różne aplikacje we współużytkowanej puli wątków lub gdy opuszczasz ContextClassLoader null), niektóre chodzą po stosie (co kończy się niepowodzeniem, gdy bezpośredni obiekt wywołujący klasę jest biblioteką), niektóre używają modułu ładującego klasy systemowe (co jest w porządku, pod warunkiem, że jest udokumentowane, aby używać tylko klas w CLASSPATHmodule ładującym) lub w programie ładującym klasy bootstrap, a niektóre używają nieprzewidywalnej kombinacji powyższych technik (co tylko bardziej komplikuje sprawę). Spowodowało to dużo płaczu i zgrzytania zębami.

Korzystając z takiego interfejsu API, najpierw spróbuj znaleźć przeciążenie metody, która akceptuje moduł ładujący klasę jako parametr . Jeśli nie ma rozsądnej metody, spróbuj ustawić ContextClassLoaderprzed wywołaniem interfejsu API (i zresetować go później):

ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
try {
    Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
    // call some API that uses reflection without taking ClassLoader param
} finally {
    Thread.currentThread().setContextClassLoader(originalClassLoader);
}
Yonran
źródło
5
Tak, to odpowiedź, którą wskazałbym każdemu, kto zadaje pytanie.
Marko Topolnik,
6
Ta odpowiedź koncentruje się na używaniu modułu ładującego klasy do ładowania klas (w celu utworzenia ich poprzez odbicie lub podobne), podczas gdy innym celem, do którego jest wykorzystywany (a właściwie jedynym celem, w jakim kiedykolwiek go osobiście używałem), jest ładowanie zasobów. Czy obowiązuje ta sama zasada, czy są sytuacje, w których chcesz pobierać zasoby za pomocą modułu ładującego klasy kontekstu, a nie modułu ładującego klasy wywołującej?
Egor Hans,
90

Istnieje artykuł na javaworld.com, który wyjaśnia różnicę => Którego ClassLoadera należy użyć

(1)

Programy ładujące klasy w kontekście wątku zapewniają tylne drzwi wokół schematu delegowania obciążeń klasowych.

Weźmy na przykład JNDI: jego wnętrzności są implementowane przez klasy bootstrap w rt.jar (począwszy od J2SE 1.3), ale te podstawowe klasy JNDI mogą ładować dostawców JNDI zaimplementowanych przez niezależnych dostawców i potencjalnie wdrożonych w ścieżce -clas aplikacji. Ten scenariusz wymaga nadrzędnego modułu ładującego klasy (w tym przypadku pierwotnego) w celu załadowania klasy widocznej dla jednego z jego modułów podrzędnych (na przykład systemowego). Normalne delegowanie J2SE nie działa, a obejście polega na tym, aby podstawowe klasy JNDI korzystały z ładujących kontekstowych wątków, tym samym skutecznie „tunelując” hierarchię modułu ładującego klasy w kierunku przeciwnym do właściwego delegowania.

(2) z tego samego źródła:

To zamieszanie prawdopodobnie pozostanie przez jakiś czas w Javie. Weź dowolny interfejs API J2SE z dynamicznym ładowaniem dowolnego rodzaju i spróbuj zgadnąć, jakiej strategii ładowania używa. Oto próbkowanie:

  • JNDI używa kontekstowych programów ładujących klasy
  • Class.getResource () i Class.forName () używają bieżącego modułu ładującego klasy
  • JAXP używa kontekstowych programów ładujących (od J2SE 1.4)
  • java.util.ResourceBundle używa bieżącego modułu ładującego klasy dzwoniącego
  • Procedury obsługi adresów URL określone przez właściwość systemową java.protocol.handler.pkgs są wyszukiwane tylko w bootstrap i systemowych modułach ładujących klasy
  • Interfejs API Java do serializacji domyślnie korzysta z bieżącego modułu ładującego klasy dzwoniącego
Timour
źródło
Ponieważ sugeruje się, że obejście polega na tym, aby podstawowe klasy JNDI korzystały z ładujących kontekst wątków, nie zrozumiałem, jak to pomaga w tym przypadku. Chcemy załadować klasy dostawców implementacji za pomocą nadrzędnego programu ładującego klasy, ale nie są one widoczne dla nadrzędnego programu ładującego klasy . Jak więc możemy załadować je za pomocą rodzica, nawet jeśli ustawimy ten nadrzędny moduł ładujący w kontekście moduł ładujący wątek.
Sunny Gupta,
6
@SAM, sugerowane obejście jest wręcz przeciwne do tego, co mówisz na końcu. To nie bootstrapmoduł ładujący klasy nadrzędnej jest ustawiony jako moduł ładujący klasy kontekstowej, ale systemmoduł ładujący klasy potomnej, który Threadjest konfigurowany. Te JNDIzajęcia są następnie upewniając się, aby użyć Thread.currentThread().getContextClassLoader(), aby załadować klasy implementacji JNDI dostępne na ścieżce klasy.
Ravi Thapliyal
„Normalne delegowanie J2SE nie działa”. Czy mogę wiedzieć, dlaczego to nie działa? Ponieważ Bootstrap ClassLoader może ładować tylko klasę z pliku rt.jar i nie może załadować klasy z -classpath aplikacji? Dobrze?
YuFeng Shen
37

Dodając do odpowiedzi @David Roussel, klasy mogą być ładowane przez wiele programów ładujących klasy.

Pozwala zrozumieć, jak działa moduł ładujący klasy .

Z bloga javina Paula w javarevisited:

wprowadź opis zdjęcia tutaj

wprowadź opis zdjęcia tutaj

ClassLoader przestrzega trzech zasad.

Zasada delegowania

Klasa jest ładowana w Javie, gdy jest potrzebna. Załóżmy, że masz klasę specyficzną dla aplikacji o nazwie Abc.class, pierwsze żądanie załadowania tej klasy przyjdzie do Application ClassLoader, który przekaże swojemu nadrzędnemu rozszerzeniu ClassLoader, który następnie przekazuje do modułu ładującego klasy Primordial lub Bootstrap

  • Bootstrap ClassLoader jest odpowiedzialny za ładowanie standardowych plików klas JDK z rt.jar i jest nadrzędny dla wszystkich programów ładujących klasy w Javie. Moduł ładujący klasy bootstrap nie ma żadnych rodziców.

  • Rozszerzenie ClassLoader przekazuje żądanie załadowania klasy do swojego rodzica, Bootstrap, a jeśli się nie powiedzie, ładuje klasę z katalogu jre / lib / ext lub dowolnego innego katalogu wskazanego przez właściwość systemową java.ext.dirs

  • Moduł ładujący klasy systemu lub aplikacji i jest odpowiedzialny za ładowanie klas aplikacji z zmiennej środowiskowej CLASSPATH, opcji -classpath lub -cp, atrybutu Class-Path pliku manifestu w pliku JAR.

  • Moduł ładujący klasy aplikacji jest dzieckiem rozszerzenia ClassLoader i jest implementowany przez sun.misc.Launcher$AppClassLoaderklasę.

UWAGA: Z wyjątkiem modułu ładującego klasy Bootstrap , który jest implementowany w języku ojczystym głównie w języku C, wszystkie moduły ładujące klasy Java są implementowane za pomocą java.lang.ClassLoader.

Zasada widoczności

Zgodnie z zasadą widoczności, Child ClassLoader może zobaczyć klasę ładowaną przez Parent ClassLoader, ale odwrotnie nie jest prawdą.

Zasada wyjątkowości

Zgodnie z tą zasadą klasa ładowana przez Parent nie powinna być ponownie ładowana przez Child ClassLoader

Ravindra babu
źródło