Dlaczego tak trudno jest to zrobić w Javie? Jeśli chcesz mieć dowolny system modułowy, musisz mieć możliwość dynamicznego ładowania plików JAR. Powiedziano mi, że można to zrobić, pisząc własne ClassLoader
, ale jest to dużo pracy dla czegoś, co powinno (przynajmniej moim zdaniem) być tak proste, jak wywołanie metody z plikiem JAR jako argumentem.
Wszelkie sugestie dotyczące prostego kodu, który to robi?
java
jar
classloader
Allain Lalonde
źródło
źródło
Odpowiedzi:
Przyczyną tego jest bezpieczeństwo. Programy ładujące klasy mają być niezmienne; nie powinieneś być w stanie nie chcąc dodawać do niego klas w czasie wykonywania. Jestem bardzo zaskoczony, że działa z systemowym modułem ładującym. Oto, jak to robisz, tworząc własny moduł ładujący dziecko:
Bolesne, ale jest.
źródło
URLClassLoader child = new URLClassLoader (new URL[] {new URL("file://./my.jar")}, Main.class.getClassLoader());
założenia, że plik jar jest wywoływanymy.jar
i znajduje się w tym samym katalogu.Poniższe rozwiązanie jest hackerskie, ponieważ wykorzystuje odbicie do obejścia enkapsulacji, ale działa bezbłędnie:
źródło
URLClassLoader.class.getDeclaredMethod("addURL", URL.class)
nielegalnym użyciem refleksji i zawiedzie w przyszłości.Powinieneś spojrzeć na OSGi , np. Zaimplementowane w platformie Eclipse . Robi dokładnie to. Możesz instalować, odinstalowywać, uruchamiać i zatrzymywać tak zwane pakiety, które są faktycznie plikami JAR. Ale robi to nieco więcej, ponieważ oferuje np. Usługi, które można dynamicznie wykrywać w plikach JAR w czasie wykonywania.
Lub zobacz specyfikację Java Module System .
źródło
Co powiesz na framework ładujący klasy JCL ? Muszę przyznać, że go nie użyłem, ale wygląda obiecująco.
Przykład użycia:
źródło
Oto wersja, która nie jest przestarzała. Zmodyfikowałem oryginał, aby usunąć przestarzałą funkcjonalność.
źródło
Podczas gdy większość wymienionych tutaj rozwiązań to albo hacki (przed JDK 9) trudne do skonfigurowania (agenty), albo po prostu już nie działają (po JDK 9), uważam za naprawdę szokujące, że nikt nie wspomniał o jasno udokumentowanej metodzie .
Możesz utworzyć niestandardowy moduł ładujący klasy systemowej, a następnie możesz robić, co chcesz. Nie wymaga refleksji, a wszystkie klasy korzystają z tego samego modułu ładującego klasy.
Podczas uruchamiania maszyny JVM dodaj tę flagę:
Moduł ładujący klasy musi mieć konstruktor akceptujący moduł ładujący, który musi być ustawiony jako jego element nadrzędny. Konstruktor zostanie wywołany podczas uruchamiania JVM i przekaże prawdziwy system ładujący klasy, klasa główna zostanie załadowana przez moduł ładujący niestandardowy.
Aby dodać słoiki, po prostu zadzwoń
ClassLoader.getSystemClassLoader()
i rzuć je na swoją klasę.Sprawdź tę implementację, aby uzyskać starannie spreparowany moduł ładujący klasy. Uwaga: możesz zmienić
add()
metodę na publiczną.źródło
W Javie 9 odpowiedzi z
URLClassLoader
teraz dają błąd taki jak:Jest tak, ponieważ zmieniły się używane ładowarki klas. Zamiast tego, aby dodać do modułu ładującego klasy systemowe, można użyć interfejsu API Instrumentacji za pośrednictwem agenta.
Utwórz klasę agenta:
Dodaj META-INF / MANIFEST.MF i umieść go w pliku JAR z klasą agenta:
Uruchom agenta:
Używa biblioteki byte-buddy-agent, aby dodać agenta do działającej maszyny JVM:
źródło
Najlepsze, co znalazłem, to org.apache.xbean.classloader.JarFileClassLoader, który jest częścią projektu XBean .
Oto krótka metoda, której użyłem w przeszłości, aby utworzyć moduł ładujący klasy ze wszystkich plików lib w określonym katalogu
Następnie, aby użyć modułu ładującego klasy, po prostu wykonaj:
źródło
Jeśli pracujesz na Androidzie, działa następujący kod:
źródło
Oto krótkie obejście metody Allain w celu zapewnienia jej zgodności z nowszymi wersjami Javy:
Pamiętaj, że opiera się on na wiedzy na temat wewnętrznej implementacji konkretnej maszyny JVM, więc nie jest idealna i nie jest rozwiązaniem uniwersalnym. Jest to jednak szybkie i łatwe obejście, jeśli wiesz, że zamierzasz używać standardowego OpenJDK lub Oracle JVM. Może się również zepsuć w przyszłości, gdy zostanie wydana nowa wersja JVM, dlatego należy o tym pamiętać.
źródło
Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make void jdk.internal.loader.ClassLoaders$AppClassLoader.appendToClassPathForInstrumentation(java.lang.String) accessible: module java.base does not "opens jdk.internal.loader" to unnamed module @18ef96
Rozwiązanie zaproponowane przez jodonnell jest dobre, ale należy je nieco ulepszyć. Wykorzystałem ten post do udanego opracowania mojej aplikacji.
Przypisz bieżący wątek
Najpierw musimy dodać
lub nie będziesz mógł załadować zasobu (takiego jak spring /ext.xml) przechowywanego w słoiku.
Nie zawiera
twoje słoiki do modułu ładującego klasy nadrzędnej, inaczej nie zrozumiesz, kto co ładuje.
zobacz także Problem z przeładowaniem słoika za pomocą URLClassLoader
Jednak środowisko OSGi pozostaje najlepszym sposobem.
źródło
Inna wersja hackish rozwiązania Allain, która działa również na JDK 11:
W JDK 11 daje pewne ostrzeżenia o wycofaniu, ale służy jako rozwiązanie tymczasowe dla tych, którzy używają rozwiązania Allain w JDK 11.
źródło
Kolejne działające rozwiązanie z wykorzystaniem Instrumentacji, które działa dla mnie. Ma tę zaletę, że modyfikuje wyszukiwanie modułu ładującego klasy, unikając problemów z widocznością klas dla klas zależnych:
Utwórz klasę agenta
W tym przykładzie musi znajdować się w tym samym słoju wywołanym z wiersza poleceń:
Zmodyfikuj plik MANIFEST.MF
Dodanie odwołania do agenta:
Właściwie korzystam z Netbeans, więc ten post pomaga zmienić sposób manifestu.mf
Bieganie
Launcher-Agent-Class
Jest obsługiwana tylko w JDK 9+ i jest odpowiedzialny za ładowanie agenta bez wyraźnego zdefiniowania go w wierszu poleceń:Sposób, w jaki działa JDK 6+, polega na zdefiniowaniu
-javaagent
argumentu:Dodanie nowego słoika w Runtime
Następnie możesz dodać słoik, jeśli to konieczne, za pomocą następującego polecenia:
Nie znalazłem żadnych problemów z korzystaniem z tego w dokumentacji.
źródło
Jeśli ktoś będzie tego szukał w przyszłości, ten sposób działa dla mnie z OpenJDK 13.0.2.
Mam wiele klas, które muszę dynamicznie tworzyć w czasie wykonywania, każda potencjalnie z inną ścieżką klas.
W tym kodzie mam już obiekt o nazwie paczka, który przechowuje metadane dotyczące klasy, którą próbuję załadować. Metoda getObjectFile () zwraca położenie pliku klasy dla klasy. Metoda getObjectRootPath () zwraca ścieżkę do katalogu bin / zawierającego pliki klas zawierające klasę, którą próbuję utworzyć. Metoda getLibPath () zwraca ścieżkę do katalogu zawierającego pliki jar stanowiące ścieżkę klas dla modułu, którego klasa jest częścią.
Używałem wcześniej zależności Maven: org.xeustechnologies: jcl-core: 2.8, ale po przejściu przez JDK 1.8 czasami się zawiesił i nigdy nie powrócił, utknął „czekając na referencje” w Reference :: waitForReferencePendingList ().
Przechowuję również mapę programów ładujących klasy, aby można było ich ponownie użyć, jeśli klasa, którą próbuję utworzyć, znajduje się w tym samym module co klasa, którą już utworzyłem, co poleciłbym.
źródło
proszę spojrzeć na ten projekt, który rozpocząłem: proxy-object lib
Ta biblioteka załaduje jar z systemu plików lub dowolnej innej lokalizacji. Poświęci moduł ładujący dla słoika, aby upewnić się, że nie ma konfliktów bibliotek. Użytkownicy będą mogli utworzyć dowolny obiekt z załadowanego słoika i wywołać dowolną metodę na nim. Ta biblioteka została zaprojektowana do ładowania słoików skompilowanych w Javie 8 z bazy kodu obsługującej Javę 7.
Aby utworzyć obiekt:
ObjectBuilder obsługuje metody fabryczne, wywoływanie funkcji statycznych i implementacje interfejsu zwrotnego. Będę publikować więcej przykładów na stronie readme.
źródło
Może to być spóźniona odpowiedź, mogę to zrobić w ten sposób (prosty przykład dla fastutil-8.2.2.jar) za pomocą klasy jhplot.Web z DataMelt ( http://jwork.org/dmelt )
Zgodnie z dokumentacją plik ten zostanie pobrany w „lib / user”, a następnie załadowany dynamicznie, dzięki czemu można natychmiast rozpocząć korzystanie z klas z tego pliku jar w tym samym programie.
źródło
Musiałem załadować plik jar w czasie wykonywania dla java 8 i java 9+ (powyższe komentarze nie działają dla obu tych wersji). Oto metoda, aby to zrobić (używając Spring Boot 1.5.2, jeśli może się to odnosić).
źródło
Ja osobiście uważam, że java.util.ServiceLoader wykonuje to zadanie całkiem dobrze. Można dostać przykład tutaj .
źródło