Sporticus Geschrieben 13. April 2011 Geschrieben 13. April 2011 Hi Community, ich bin noch ganz neu hier und habe mich zu meinem momentanem Problem schon mal ein wenig umgesehen, allerdings keine Lösung gefunden (weder hier noch bei Google). Ich programmiere mir momentan ein Programm, welches anhand von Java-Klassen ein Klassen-UML-Diagramm erstellen soll. Das erstellen und pipapo funktioniert einwandfrei. Allerdings steh ich noch vor dem Rätsel, wie ich eine class aus einem x beliebigen Projekt/Ordner/etc. laden kann und in meinem Programm verwenden kann. (Ich kann das Programm nur auf Klassen aus dem aktuellen Projekt laufen lassen) Bisher hab ich es probiert, die x beliebige Klasse über den ClassLoader zu laden, allerdings hab ich festgestellt, dass dieser nur Klassen aus dem aktuellen Projekt laden kann! Ich würde mich über ein paar Anregungen, Vorschläge, etc. sehr freuen. Danke Zitieren
flashpixx Geschrieben 13. April 2011 Geschrieben 13. April 2011 Nimm dafür den Classloader (hier ist das nett erklärt Java Classloader ). Einfach die statische Methode forName oder loadClass (von Class) verwenden. Zitieren
Sporticus Geschrieben 13. April 2011 Autor Geschrieben 13. April 2011 Hi flashpixx, danke ersteinmal für deine schnelle Antwort. Die Seite, die du mir gezeigt hast, hab ich auch schon einmal gefunden, allerdings dabei nur überflogen. Ich habe jetzt versucht das, was auf dieser Seite steht, umzusetzen. Mein Code sieht momentan so aus: public class JarClassLoader extends ClassLoader { /** * @param jarFile - enthält Pfad zur *.class Datei: C:\Beispiel\Beispiel.class * @param binaryClassName - Format: java.lang.ClassLoader * @return * @throws ClassNotFoundException */ public Class<?> loadClass(File jarFile, String binaryClassName) throws ClassNotFoundException { // --- // lesen über den ClassLoader Class<?> findLoadedClass = findLoadedClass(binaryClassName); if(findLoadedClass != null) { return findLoadedClass; } // --- // lesen über den InputStream byte[] data = readClassFromFile(jarFile, binaryClassName); if(data == null) { throw new ClassNotFoundException(binaryClassName); } // --- // Class definieren Class<?> defineClass = defineClass(binaryClassName, data, 0, data.length); System.out.println(defineClass.getName()); return defineClass; } private byte[] readClassFromFile(File jarFile, String binaryClassName) { String fileName = jarFile.getAbsolutePath(); InputStream in = getClass().getClassLoader().getResourceAsStream(fileName); if(in != null) { try { List<Integer> byteList = new LinkedList<Integer>(); int oneByte = 0; while((oneByte = in.read()) != -1) { byteList.add(new Integer(oneByte)); } byte[] data = new byte[byteList.size()]; for(int index = 0; index < byteList.size(); index++) { data[index] = byteList.get(index).byteValue(); } return data; } catch(IOException e) { e.printStackTrace(); } finally { IOUtils.closeQuietly(in); } } return null; } } Allerdings bleibt die ClassNotFoundException erhalten Vielleicht bin ich auch einfach so auf den Code fokusiert, dass ich meinen Fehler einfach übersehe. Aber vielleicht hat einer von euch einen besseren Blick darauf. Zitieren
flashpixx Geschrieben 13. April 2011 Geschrieben 13. April 2011 Eine Klasse, die via Class.fromName geladen wird, liegt normalerweise im Classpath, d.h. Du solltest Die Klassen, die Du laden willst erst einmal so versuchen zu laden. Zitieren
Sporticus Geschrieben 13. April 2011 Autor Geschrieben 13. April 2011 Das hab ich als aller erstes schon versucht. Das hat allerdings auch nicht funktioniert. Meine Vermutung: Da die Datei.class nicht in dem gleichen Projekt liegt, in dem das Programm ausgeführt wird, muss ich den vollständigen Pfad zu dieser Datei.class angeben. Was ich gerade noch ausprobiert habe: InputStream in = null; try { in = new FileInputStream(jarFile); // File über den FileInputStream einlesen?! if(in != null) { List<Integer> byteList = new LinkedList<Integer>(); int oneByte = 0; while((oneByte = in.read()) != -1) { byteList.add(new Integer(oneByte)); } byte[] data = new byte[byteList.size()]; for(int index = 0; index < byteList.size(); index++) { data[index] = byteList.get(index).byteValue(); } Class<?> defineClass = defineClass(StringUtils.removeEnd(jarFile.getName(), ".class"), data, 0, data.length); } } catch(IOException e) { e.printStackTrace(); } finally { IOUtils.closeQuietly(in); } Danach bekomm ich dann eine NoClassDefFoundError... Exception in thread "main" java.lang.NoClassDefFoundError: Test (wrong name: de/test/Test) at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632) at java.lang.ClassLoader.defineClass(ClassLoader.java:616) at java.lang.ClassLoader.defineClass(ClassLoader.java:466) ... Zitieren
flashpixx Geschrieben 13. April 2011 Geschrieben 13. April 2011 Das hab ich als aller erstes schon versucht. Das hat allerdings auch nicht funktioniert. Meine Vermutung: Da die Datei.class nicht in dem gleichen Projekt liegt, in dem das Programm ausgeführt wird, muss ich den vollständigen Pfad zu dieser Datei.class angeben. Eine Klasse wird nur über den Namen (und ggf. Packagenamen) und nicht den Dateinamen geladen. Folgende beiden Codes funktionieren: class run { public static void main(String[] args) { try { Class.forName("load"); } catch (Exception e) { e.printStackTrace(); } } } und die entsprechende load-Klasse: class load { int x = 5; } Sofern die load-Klasse im Classpath liegt, wird sie auch geladen. Zitieren
Sporticus Geschrieben 14. April 2011 Autor Geschrieben 14. April 2011 Also ich hab mir jetzt nochmal ein paar Seiten im Internet zum Thema "File.class während der Laufzeit zum Classpath hinzufügen" angesehen. Soweit ich das aus meiner jetzigen Sicht sagen kann, ist es nur möglich eine File.jar während der Laufzeit zum Classpath hinzuzufügen. Dieses Beispiel habe ich gefunden, um ein Jar-File in den Classpath mit hinzuzufügen (Website): private static final Class<?>[] parameters = new Class[] { URL.class }; public static void addFile(String s) throws IOException { File f = new File(s); addFile(f); } public static void addFile(File f) throws IOException { addURL(f.toURI().toURL()); } public static void addURL(URL u) throws IOException { URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader(); Class<?> sysclass = URLClassLoader.class; try { Method method = sysclass.getDeclaredMethod("addURL", parameters); method.setAccessible(true); method.invoke(sysloader, new Object[] { u }); } catch(Throwable t) { t.printStackTrace(); throw new IOException("Error, could not add URL to system classloader"); } } Ein anderer Lösungsansatz, um ein Jar-File zu laden: (Website) /** * @param args */ public static void main(String[] args) { try { Class.forName("com.google.inject.Injector"); System.out.println("Found"); } catch (ClassNotFoundException e) { System.out.println(e.getMessage() + " not found..."); } addJarsToClassPath(Thread.currentThread().getContextClassLoader(), new File[]{new File("D:/stuff/google/guice/1.0/guice-1.0.jar")}); try { Class.forName("com.google.inject.Injector"); System.out.println("Found"); } catch (ClassNotFoundException e) { System.out.println(e.getMessage() + " not found..."); } } private static void addJarsToClassPath(ClassLoader classLoader, File[] jars) { if (classLoader instanceof URLClassLoader) { try { Method addUrlMethod = URLClassLoader.class.getDeclaredMethod("addURL", new Class[] { URL.class }); addUrlMethod.setAccessible(true); if (null != addUrlMethod) { for (File jar : jars) { try { addUrlMethod.invoke(classLoader, jar.toURI().toURL()); } catch (Exception e) { e.printStackTrace(); } } } } catch (Exception e) { e.printStackTrace(); } } } In meinem Fall hat keines der beiden Beispiele funktioniert. Sofern die load-Klasse im Classpath liegt, wird sie auch geladen. Wenn ich eine ClassNotFoundException bekomme, vermute ich mal, dass die Klasse dann nicht im Classpath liegt. Kann ich diese Klasse denn dann nachträglich noch in den Classpath mit aufnehmen? Zitieren
Sporticus Geschrieben 14. April 2011 Autor Geschrieben 14. April 2011 Mir ist gerade aufgefallen, dass ich im prinzip genau die Funktion brauche, die Eclipse bei der Suche nach einem String (z.B.: enthalten in Methoden, Fields, etc.). Ist nur die Frage, ob bei der Suchefunktion von Eclipse die Datei geparsed wird, oder via den ClassLoader o.ä. geladen wird? Zitieren
flashpixx Geschrieben 14. April 2011 Geschrieben 14. April 2011 (bearbeitet) Wenn ich eine ClassNotFoundException bekomme, vermute ich mal, dass die Klasse dann nicht im Classpath liegt. Ja. Kann ich diese Klasse denn dann nachträglich noch in den Classpath mit aufnehmen? Soweit ich das im Kopf habe, geht das nicht, dass man dynamisch den Classpath erweitern kann. Zur Laufzeit ist meines Wissens keine Änderung möglich. Ein Jar ist kein Dateistream im eigentlichen Sinne. Ein Jar ist ein Zip-Archiv, wobei man eben dort eine ganze Reihe an einzelnen Klassen hat, die aber durch Packages organisiert sein können. D.h. wenn Du die Klassen aus einem Jar einbinden willst, dann musst Du einen eigenen Classloader schreiben und damit ggf den Standard-Classloader ersetzen, denn Wenn z.B. eine Klasse aus dem Jar instantiiert wird und eine Klasse, die ebenfalls innerhalb des Jars liegt, instantiiert würde der Standard-Classloader eine Exception werfen, da er diese weitere Klasse nicht erreichen kann. Das ganze halte ich aber für extrem unhandlich und im Sinne, dass Du eine externe Komponente nutzen willst, auch für nicht sinnvoll. Bei externen Komponenten wird man via Class.forName, die entsprechende Klasse laden, wobei man davon ausgeht, dass die Klasse/en im Classpath liegen, z.B. bei JDBC verfährt man so. Liegt der JDBC Treiber nicht im Classpath erhält man eine Exception und der User muss dann eben die fehlenden Daten ergänzen Bearbeitet 14. April 2011 von flashpixx Zitieren
kingofbrain Geschrieben 14. April 2011 Geschrieben 14. April 2011 Soweit ich das im Kopf habe, geht das nicht, dass man dynamisch den Classpath erweitern kann. Zur Laufzeit ist meines Wissens keine Änderung möglich. Doch, es ist schon möglich, zur Laufzeit den Classpath zu erweitern. Jeder Application Server mit Hot Deploy macht so etwas. Der OP kann sich z.B. mal das Classloading im JBoss anschauen. Es ist allerdings nicht trivial, so etwas zu ändern. Man sollte schon wissen, was man da macht. Schöne Grüße, Peter Zitieren
flashpixx Geschrieben 14. April 2011 Geschrieben 14. April 2011 Jeder Application Server mit Hot Deploy macht so etwas. Der OP kann sich z.B. mal das Classloading im JBoss anschauen. Genau das macht das Hot Deplayment doch eben nicht. Beim Hot Deployment existiert ein eigener Classloader, der auch dafür sorge trägt, dass dann Klassen aus unterschiedlichen Pfaden geladen / entladen werden. Beim Entladen läuft dann der GC drüber und entfernt die Objekte aus dem Speicher. Der Classloader koordiniert die Zugriffe auf der Dateiebene und sorgt für das Instantiieren. Formal wird nicht der Classpath erweitert, sondern der Classloader lädt anhand der Struktur die passenden Klassen (ggf. auch mehrfach). Zitieren
kingofbrain Geschrieben 15. April 2011 Geschrieben 15. April 2011 Guten Morgen! Da hast Du eine andere Definition von Classpath als ich. Für mich ist der Classpath die Summe an Orten, an denen nach Klassen gesucht werden kann. Es gibt einen Bootclasspath, der beim Start des Servers feststeht, und den erweiterten Classpath, der durch dynamisches nachladen aufgebaut wird. Aber wir sind uns ja soweit einig, dass die Anzahl der für das Classloading verfügbaren Klassen während der Laufzeit einer Anwendung durch einen entsprechenden Classloader erweitert werden kann. Das Abräumen mit dem GC funktioniert übrigens bei geladenen Klassen so nicht. Diese landen in der Permanent Generation und sind dann bei wiederholten Hot Deploys die Ursache für entsprechende OutOfMemoryErrors. Schöne Grüße, Peter Zitieren
Sporticus Geschrieben 15. April 2011 Autor Geschrieben 15. April 2011 Guten Morgen, gibt es nicht irgendwelche Open Source Projekte/Programme, bei denen sowas gemacht wird? Dann könnte man sich den Code dort einfach mal ansehen und könnte dann sagen, wie es funktioniert. Ich hab es ja schon einmal angesprochen, dass Eclipse eine Suche verwendet, die auch den Klasseninhalt (Variablen, Methoden) durchsucht und das Ganze im kompletten Workspace. Allerdings könnte es auch sein, dass hier das File einfach geparsed wird. Zitieren
flashpixx Geschrieben 15. April 2011 Geschrieben 15. April 2011 Da hast Du eine andere Definition von Classpath als ich. *g* stimmt Das Abräumen mit dem GC funktioniert übrigens bei geladenen Klassen so nicht. Diese landen in der Permanent Generation und sind dann bei wiederholten Hot Deploys die Ursache für entsprechende OutOfMemoryErrors. Das stimmt, man muss im Grunde erst mal alle Referenzen auf die Klasse aus dem Speicher entfernen, bevor der GC dann wirklich aufräumen kann. Da der GC letztendlich zyklische via Mark & Sweep / Compact durch alle Objektreferenzen durchgeht, muss man bevor man eine Klasse entfernt eben erst einmal alle Objekte als "not-used" markieren, damit der GC sie entfernen kann. Erst wenn das geschehen ist, kann man die Klasse entladen. @Topic: Nein Eclipse durchsucht nicht, sondern die Klasse wird während der Codings immer wieder kompiliert, denn z.B. korrekte Einrückung kann nur dadurch korrektes Parsen & Compilieren fest gestellt werden (Blockbildung Dyck-Sprache ist nur mit einer Kontextfreie Grammatik verarbeitbar). Die Klasse wird nicht geparst sondern wirklich instanziiert und dann zur Laufzeit verarbeitet. Zitieren
kingofbrain Geschrieben 15. April 2011 Geschrieben 15. April 2011 [...] gibt es nicht irgendwelche Open Source Projekte/Programme, bei denen sowas gemacht wird? Dann könnte man sich den Code dort einfach mal ansehen und könnte dann sagen, wie es funktioniert.[...] Schau Dir einfach den Classloading Mechanismus vom JBoss an. Der ist Open Source und macht so etwas. Du solltest allerdings ein bisschen Grundwissen über Classloading anschauen. Schöne Grüße, Peter Zitieren
Sporticus Geschrieben 27. April 2011 Autor Geschrieben 27. April 2011 Die Lösung zu meinem Problem hab ich auf dieser Seite gefunden, falls mal jemand vor dem gleichen Problem stehen sollte www.torsten-horn.de Zitieren
Empfohlene Beiträge
Dein Kommentar
Du kannst jetzt schreiben und Dich später registrieren. Wenn Du ein Konto hast, melde Dich jetzt an, um unter Deinem Benutzernamen zu schreiben.