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
ClassNotFoundException
throwClassNotFoundException
. - 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. -
findResource
ifindResources
- 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
findLoadedClass
klasyloadClass
wywołujefindLoadedClass
aby sprawdzić, czy klasa o tej nazwie została już załadowana przez ten loadloader. Jeśli to się powiedzie, wynikowy obiektClass
jest zwracany do requestera. - Metoda
loadClass
deleguje następnie do nadrzędnegoloadClass
klasy, wywołując wywołanieloadClass
. Jeśli rodzic może poradzić sobie z żądaniem, zwróci obiektClass
który następnie zostanie zwrócony do żądającego. - Jeśli nadrzędny
findClass
nie może załadować klasy,findClass
wywołuje następnie naszą metodę zastępowaniafindClass
, przekazując nazwę klasy do załadowania. - Jeśli żądana nazwa pasuje do
this.classname
, wywołujemydefineClass
aby załadować rzeczywistą klasę z tablicy bajtówthis.classfile
. Wynikowy obiektClass
jest 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();
}