2 Stimmen

Warum funktioniert das Laden von Java-Klassen unter Linux nicht, unter Windows aber schon?

Ich habe eine Java-Webanwendung (mit Spring), die mit Jetty bereitgestellt wird. Wenn ich versuche, es auf einem Windows-Rechner laufen alles funktioniert wie erwartet, aber wenn ich versuche, den gleichen Code auf meinem Linux-Rechner laufen, es scheitert wie folgt:

\[normal startup output\]
11:16:39.657 INFO   \[main\] org.mortbay.jetty.servlet.ServletHandler$Context.log>(ServletHandler.java:1145) >16> Set web app root system property: 'webapp.root' = \[/path/to/working/dir\]
java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at org.mortbay.start.Main.invokeMain(Main.java:151)
        at org.mortbay.start.Main.start(Main.java:476)
        at org.mortbay.start.Main.main(Main.java:94)
Caused by: java.lang.ExceptionInInitializerError
        at org.springframework.web.util.Log4jWebConfigurer.initLogging(Log4jWebConfigurer.java:129)
        at org.springframework.web.util.Log4jConfigListener.contextInitialized(Log4jConfigListener.java:51)
        at org.mortbay.jetty.servlet.WebApplicationContext.doStart(WebApplicationContext.java:495)
        at org.mortbay.util.Container.start(Container.java:72)
        at org.mortbay.http.HttpServer.doStart(HttpServer.java:708)
        at org.mortbay.util.Container.start(Container.java:72)
        at org.mortbay.jetty.Server.main(Server.java:460)
        ... 7 more
Caused by: org.apache.commons.logging.LogConfigurationException: org.apache.commons.logging.LogConfigurationException: No suitable Log constructor \[Ljava.lang.Class;@15311bd for org.apache.commons.logging.impl.Log4JLogger (Caused by java.lang.NoClassDefFoundError: org/apache/log4j/Category) (Caused by org.apache.commons.logging.LogConfigurationException: No suitable Log constructor \[Ljava.lang.Class;@15311bd for org.apache.commons.logging.impl.Log4JLogger (Caused by java.lang.NoClassDefFoundError: org/apache/log4j/Category))
        at org.apache.commons.logging.impl.LogFactoryImpl.newInstance(LogFactoryImpl.java:543)
        at org.apache.commons.logging.impl.LogFactoryImpl.getInstance(LogFactoryImpl.java:235)
        at org.apache.commons.logging.impl.LogFactoryImpl.getInstance(LogFactoryImpl.java:209)
        at org.apache.commons.logging.LogFactory.getLog(LogFactory.java:351)
        at org.springframework.util.SystemPropertyUtils.(SystemPropertyUtils.java:42)
        ... 14 more
Caused by: org.apache.commons.logging.LogConfigurationException: No suitable Log constructor \[Ljava.lang.Class;@15311bd for org.apache.commons.logging.impl.Log4JLogger (Caused by java.lang.NoClassDefFoundError: org/apache/log4j/Category)
        at org.apache.commons.logging.impl.LogFactoryImpl.getLogConstructor(LogFactoryImpl.java:413)
        at org.apache.commons.logging.impl.LogFactoryImpl.newInstance(LogFactoryImpl.java:529)
        ... 18 more
Caused by: java.lang.NoClassDefFoundError: org/apache/log4j/Category
        at java.lang.Class.getDeclaredConstructors0(Native Method)
        at java.lang.Class.privateGetDeclaredConstructors(Class.java:2389)
        at java.lang.Class.getConstructor0(Class.java:2699)
        at java.lang.Class.getConstructor(Class.java:1657)
        at org.apache.commons.logging.impl.LogFactoryImpl.getLogConstructor(LogFactoryImpl.java:410)
        ... 19 more
Caused by: java.lang.ClassNotFoundException: org.apache.log4j.Category
        at java.net.URLClassLoader$1.run(URLClassLoader.java:200)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:252)
        at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:320)
        ... 24 more
\[shutdown output\]

Ich habe die Anwendung mit java -verbose:class und laut dieser Ausgabe wird org.apache.log4j.Category aus dem log4j-JAR in meinem /WEB-INF/lib geladen, kurz bevor die erste Ausnahme ausgelöst wird.

Die Java-Versionen auf den beiden Rechnern sind leicht unterschiedlich. Beide Maschinen haben Suns Java, die Linux-Maschine hat 1.6.0_10, während die Windows-Maschine 1.6.0_08 hat, oder vielleicht 07 oder 06, ich kann mich gerade nicht an die genaue Nummer erinnern und habe die Maschine nicht zur Hand. Aber auch wenn die Nebenversionen der Javas leicht unterschiedlich sind, sollte der Code nicht so ausfallen. Versteht jemand, was hier falsch ist?

5voto

Aaron Digulla Punkte 308693

Sie müssen verstehen, dass ein Classloader nicht alles sehen kann; er kann nur sehen, was ein übergeordneter Classloader geladen hat oder was er selbst geladen hat. Wenn Sie also zwei Classloader haben, sagen wir einen für Jetty und einen anderen für Ihre Webapp, kann Ihre Webapp log4j sehen (da das JAR die WEB-INF/lib ist), aber der Classloader von Jetty kann das nicht.

Wenn es Ihnen gelingt, eine Klasse für Jetty verfügbar zu machen (z.B. etwas in der DB-Schicht), die log4j verwendet, aber im Kontext (und Classloader) von Jetty läuft, erhalten Sie einen Fehler.

Um dies zu debuggen, setzen Sie einen Haltepunkt in org.springframework.web.util.Log4jWebConfigurer.initLogging(). Wenn Sie können, kopieren Sie den Quellcode dieser Klasse in Ihr Projekt (vergessen Sie nicht, ihn anschließend zu löschen) und fügen Sie diese Zeile hinzu:

ClassLoader cl = Thread.currentThread().getContextClassLoader();

Schauen Sie sich das cl-Objekt in Ihrem Debugger an. Das sollte Ihnen einige Informationen darüber geben, wer es erstellt hat. Meine Vermutung ist, dass es sich um den Classloader von Jetty handelt.

[EDIT] Beachten Sie, dass Sie in ein anderes Durcheinander zu bekommen, wenn Sie log4j in beiden Classloader haben: In diesem Fall haben Sie zwei Klassen mit dem dieselbe Namen, die Objekte erzeugen, die nicht zuweisungskompatibel sind! Stellen Sie also sicher, dass es nur eine einzige Instanz dieses Jars gibt oder dass Instanzen von log4j niemals zwischen den beiden Kontexten übergeben werden (was normalerweise nicht möglich ist).

0 Stimmen

Herzlichen Dank! Das war es im Wesentlichen. Es gab auch einen Unterschied in den Versionen von Jetty, 5.1.11 vs. 5.1.14 unter Linux (was ich wirklich hätte überprüfen sollen), und .14 lud log4j nicht standardmäßig, wie .11 es tat. Die Freigabe des log4j JAR für den Jetty Classloader hat das Problem behoben.

0 Stimmen

Hervorragendes Argument. Ich werde das in Zukunft auf jeden Fall im Hinterkopf behalten. Aber wie Sie schon sagten, ist es unwahrscheinlich, dass Log4J-Instanzen zwischen Jetty und der Anwendung weitergegeben werden, also sollte es in diesem Fall kein Problem sein. Nochmals vielen Dank für Ihren Rat.

1voto

mxc Punkte 963

Dies scheint ein klassisches Classloader-Problem zu sein. Es könnte daran liegen, dass zuerst eine andere Webanwendung geladen wird, die ebenfalls log4j verwendet, aber eine andere Version als die, die von der zu testenden Anwendung verwendet wird. Der Classloader verwendet die erste Version der Klasse, die er findet. Die Richtlinie für das Laden von Serverklassen kann normalerweise in den Konfigurationsdateien geändert werden. Sorry, ich bin ein bisschen eingerostet, aber vielleicht kann es Ihnen die richtige Richtung weisen.

  1. Stellen Sie sicher, dass keine anderen Anwendungen auf dem Webserver installiert sind,
  2. Stellen Sie sicher, dass das geladene log4j die richtige Version ist,
  3. Vergewissern Sie sich, dass Sie kein log4j irgendwo im Klassenpfad des Servers versteckt haben.

HTH

0 Stimmen

-1: App-Server verwenden dedizierte Klassenlader für jede App. Verschiedene Webanwendungen stören sich nicht gegenseitig.

0 Stimmen

Stimmt nicht - es kommt darauf an, wie der Klassenlader des Servers konfiguriert ist.

0voto

Eduard Wirch Punkte 9548

Sie verwenden auf beiden Rechnern die gleiche WAR? Haben Sie überprüft, ob die WAR-Dateien identisch sind (keine Übertragungsfehler aufgetreten sind)?

0voto

toolkit Punkte 48653

Einige zufällige Dinge, die zu beachten sind:

(1) Prüfen Sie, ob auf der Linux-Instanz noch andere Versionen von log4j außerhalb der Web-App-Verzeichnisse vorhanden sind.

(2) Wird die Apache-Commons-Protokollierung überhaupt verwendet? Sie sollten Folgendes in Betracht ziehen SLF4J stattdessen?

(3) Wurde die JAR/WAR-Datei auf irgendeine Weise beschädigt - wurde sie im ASCII- oder Binärformat per FTP übertragen?

(4) Drucken Sie die Classloader-Hierarchie in jedem Fall aus, nur um zu sehen, ob es irgendwelche Unstimmigkeiten gibt?

0voto

Peter Dolberg Punkte 1967

Auch wenn das ursprüngliche Problem für den Fragesteller gelöst wurde, möchte ich darauf hinweisen, dass eine häufige Ursache für Probleme bei der Ausführung desselben Codes unter Windows und Linux (oder Unix) die Groß- und Kleinschreibung ist. Windows ignoriert die Groß- und Kleinschreibung, während Linux oder Unix die Groß- und Kleinschreibung beachten. Das hat mich schon mehr als einmal erwischt.

Wenn Sie also ein jar oder ein Verzeichnis auf dem Klassenpfad angeben, aber es ist nicht der richtige Fall, dann schlägt es unter Linux fehl, aber unter Windows ist es erfolgreich. Dies kann auch die Quelle von FileNotFoundExceptions sein.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X