Java Language
Ładowarki klasowe
Szukaj…
Uwagi
Classloader to klasa, której głównym celem jest pośredniczenie w lokalizacji i ładowaniu klas używanych przez aplikację. Moduł ładujący klasy może także znajdować i ładować zasoby .
Standardowe klasy modułu ładującego klasy mogą ładować klasy i zasoby z katalogów w systemie plików oraz z plików JAR i ZIP. Mogą także pobierać i buforować pliki JAR i ZIP ze zdalnego serwera.
Programy ładujące klasy są zwykle połączone łańcuchem, aby JVM spróbowała załadować klasy ze standardowych bibliotek klas zamiast źródeł dostarczonych przez aplikację. Niestandardowe moduły ładujące pozwalają programiście to zmienić. Mogą także wykonywać takie czynności, jak odszyfrowanie plików kodu bajtowego i modyfikacja kodu bajtowego.
Tworzenie instancji i korzystanie z modułu ładującego klasy
Ten podstawowy przykład pokazuje, jak aplikacja może utworzyć instancję modułu ładującego klasy i użyć go do dynamicznego ładowania klasy.
URL[] urls = new URL[] {new URL("file:/home/me/extras.jar")};
Classloader loader = new URLClassLoader(urls);
Class<?> myObjectClass = loader.findClass("com.example.MyObject");
Moduł ładujący utworzony w tym przykładzie będzie miał domyślny moduł ładujący jako swój nadrzędny i najpierw spróbuje znaleźć dowolną klasę w nadrzędnym module ładującym, zanim przejdzie do „extra.jar”. Jeśli żądana klasa została już załadowana, wywołanie findClass zwróci odwołanie do poprzednio załadowanej klasy.
Wywołanie findClass może zakończyć się niepowodzeniem na różne sposoby. Najczęstsze to:
- Jeśli nie można znaleźć nazwanej klasy, wywołanie z
ClassNotFoundExceptionthrowClassNotFoundException. - Jeśli nazwana klasa zależy od innej klasy, której nie można znaleźć, wywołanie wyrzuci
NoClassDefFoundError.
Implementowanie niestandardowego modułu ładującego klasy
Każdy niestandardowy moduł ładujący musi bezpośrednio lub pośrednio rozszerzyć klasę java.lang.ClassLoader . Głównymi punktami rozszerzenia są następujące metody:
-
findClass(String)- przeciąż tę metodę, jeślifindClass(String)klasy postępuje zgodnie ze standardowym modelem delegowania do ładowania klas. -
loadClass(String, boolean)- przeciąż tę metodę, aby zaimplementować alternatywny model delegowania. -
findResourceifindResources- przeciąż te metody, aby dostosować ładowanie zasobów.
Metody defineClass , które są odpowiedzialne za ładowanie klasy z tablicy bajtów, są final aby zapobiec przeciążeniu. Każde niestandardowe zachowanie należy wykonać przed wywołaniem funkcji defineClass .
Oto proste ładowanie określonej klasy z tablicy bajtów:
public class ByteArrayClassLoader extends ClassLoader {
private String classname;
private byte[] classfile;
public ByteArrayClassLoader(String classname, byte[] classfile) {
this.classname = classname;
this.classfile = classfile.clone();
}
@Override
protected Class findClass(String classname) throws ClassNotFoundException {
if (classname.equals(this.classname)) {
return defineClass(classname, classfile, 0, classfile.length);
} else {
throw new ClassNotFoundException(classname);
}
}
}
Ponieważ tylko findClass metodę findClass , ten moduł ładujący klasy niestandardowej będzie zachowywał się w następujący sposób po loadClass .
- Metoda
findLoadedClassklasyloadClasswywołujefindLoadedClassaby sprawdzić, czy klasa o tej nazwie została już załadowana przez ten loadloader. Jeśli to się powiedzie, wynikowy obiektClassjest zwracany do requestera. - Metoda
loadClassdeleguje następnie do nadrzędnegoloadClassklasy, wywołując wywołanieloadClass. Jeśli rodzic może poradzić sobie z żądaniem, zwróci obiektClassktóry następnie zostanie zwrócony do żądającego. - Jeśli nadrzędny
findClassnie może załadować klasy,findClasswywołuje następnie naszą metodę zastępowaniafindClass, przekazując nazwę klasy do załadowania. - Jeśli żądana nazwa pasuje do
this.classname, wywołujemydefineClassaby załadować rzeczywistą klasę z tablicy bajtówthis.classfile. Wynikowy obiektClassjest następnie zwracany. - Jeśli nazwa się nie zgadza,
ClassNotFoundException.
Ładowanie zewnętrznego pliku .class
Aby załadować klasę, musimy ją najpierw zdefiniować. Klasa jest zdefiniowana przez ClassLoader . Jest tylko jeden problem, Oracle nie napisał kodu ClassLoader z tą dostępną funkcją. Aby zdefiniować klasę, musimy uzyskać dostęp do metody o nazwie defineClass() która jest prywatną metodą ClassLoader .
Aby uzyskać do niego dostęp, utworzymy nową klasę ByteClassLoader i rozszerzymy ją na ClassLoader . Teraz, gdy rozszerzyliśmy naszą klasę na ClassLoader , możemy uzyskać dostęp do prywatnych metod ClassLoader . Aby udostępnić defineClass() , stworzymy nową metodę, która będzie działać jak lustro dla prywatnej metody defineClass() . Aby wywołać metodę prywatną, potrzebujemy nazwy klasy, name , bajtów klasy, classBytes , przesunięcia pierwszego bajtu, który będzie classBytes 0 ponieważ dane classBytes[0] zaczynają się od classBytes[0] , i przesunięcia ostatniego bajtu, który będzie classBytes.lenght ponieważ reprezentuje rozmiar danych, który będzie ostatnim przesunięciem.
public class ByteClassLoader extends ClassLoader {
public Class<?> defineClass(String name, byte[] classBytes) {
return defineClass(name, classBytes, 0, classBytes.length);
}
}
Teraz mamy publiczną defineClass() . Można go wywołać, przekazując nazwę klasy i bajty klasy jako argumenty.
Powiedzmy, że mamy klasę o nazwie MyClass w pakiecie stackoverflow ...
Aby wywołać metodę, potrzebujemy bajtów klasy, dlatego tworzymy obiekt Path reprezentujący Path naszej klasy za pomocą metody Paths.get() i przekazując ścieżkę klasy binarnej jako argument. Teraz możemy pobrać bajty klasy za pomocą Files.readAllBytes(path) . Tworzymy ByteClassLoader instancję ByteClassLoader i korzystamy z utworzonej przez nas metody, defineClass() . Mamy już bajty klasy, ale aby wywołać naszą metodę, potrzebujemy również nazwy klasy, która jest podana przez nazwę pakietu (kropka) kanoniczną nazwą klasy, w tym przypadku stackoverflow.MyClass .
Path path = Paths.get("MyClass.class");
ByteClassLoader loader = new ByteClassLoader();
loader.defineClass("stackoverflow.MyClass", Files.readAllBytes(path);
Uwaga : Metoda defineClass() zwraca obiekt Class<?> . Możesz go zapisać, jeśli chcesz.
Aby załadować klasę, po prostu wywołujemy loadClass() i przekazujemy nazwę klasy. Ta metoda może ClassNotFoundException dlatego musimy użyć bloku try cath
try{
loader.loadClass("stackoverflow.MyClass");
} catch(ClassNotFoundException e){
e.printStackTrace();
}