Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
Vorwort
1 Neues in Java 7
2 Threads und nebenläufige Programmierung
3 Datenstrukturen und Algorithmen
4 Raum und Zeit
5 Dateien, Verzeichnisse und Dateizugriffe
6 Datenströme
7 Die eXtensible Markup Language (XML)
8 Dateiformate
9 Grafische Oberflächen mit Swing
10 Grafikprogrammierung
11 Netzwerkprogrammierung
12 Verteilte Programmierung mit RMI
13 RESTful und SOAP Web-Services
14 JavaServer Pages und Servlets
15 Applets
16 Datenbankmanagement mit JDBC
17 Technologien für die Infrastruktur
18 Reflection und Annotationen
19 Dynamische Übersetzung und Skriptsprachen
20 Logging und Monitoring
21 Java Native Interface (JNI)
22 Sicherheitskonzepte
23 Dienstprogramme für die Java-Umgebung
Stichwort
Download: - openbook, ca. 21,3 MB
Buch bestellen
Ihre Meinung?
<< zurück | Galileo Computing / <openbook> / Java 7 - Mehr als eine Insel | vor >>
Java 7 - Mehr als eine Insel von Christian Ullenboom Das Handbuch zu den Java SE-Bibliotheken
Java 7 - Mehr als eine Insel Galileo Computing 1433 S., 2012, geb. 49,90 Euro, ISBN 978-3-8362-1507-7 |
| 19 Dynamische Übersetzung und Skriptsprachen
|  | 19.1 Codegenerierung
|  |  | 19.1.1 Generierung von Java-Quellcode
|  |  | 19.1.2 Codetransformationen
|  |  | 19.1.3 Erstellen von Java-Bytecode
|  | 19.2 Programme mit der Compiler API übersetzen
|  |  | 19.2.1 Java Compiler API
|  |  | 19.2.2 Fehler-Diagnose
|  |  | 19.2.3 Eine im String angegebene Compiliationseinheit übersetzen
|  |  | 19.2.4 Wenn Quelle und Ziel der Speicher sind
|  | 19.3 Ausführen von Skripten
|  |  | 19.3.1 JavaScript-Programme ausführen
|  |  | 19.3.2 Groovy
|  | 19.4 Zum Weiterlesen
## 19.2 Programme mit der Compiler API übersetzen Es gibt verschiedene Gründe, warum eigene Java-Programme selbst andere Programme übersetzen müssen. Ein zentraler Grund sind Codegeneratoren, die Java-Programme erzeugen; anschließend lassen sich die Exporte übersetzen und über einen eigenen Klassenlader direkt einbinden. Auch für eine IDE ist der Zugriff auf Compilermeldungen interessant. Sie kann Java-Programme übersetzen und mögliche Fehler live im Editor anzeigen. Zum Übersetzen von Java-Programmen in Bytecode gibt es unterschiedliche Vorgehensweisen: Aufruf eines Compilers über einen externen Prozess. Das funktioniert mit jedem Compiler, ist aber langsam, da das Starten eines externen Prozesses immer problematisch ist. Zudem kann ein externer Compiler nicht den Quellcode aus dem Speicher lesen oder die Klassendateien im Speicher ablegen, sodass ein Klassenlader gleich Zugriff auf den Bytecode hat. Nutzen der standardisierten Java Compiler-API im Paket javax.tools seit Java 6. Rückgriff auf den Java-Compiler von Oracle oder Eclipse. Apache Commons Java Compiler Interface (JCI) unter http://tutego.de/go/jci ist eine Abstraktion, um mit einer API beide Compiler ansprechen zu können. Wir gehen im Folgenden von einem JDK größer gleich Version 6 aus. ### 19.2.1 Java Compiler API In Java 6 wurde eine Compiler API integriert, die erstmals im JSR-199, »Java Compiler API«, definiert wurde. Damit lässt sich der Java-Compiler über eine standardisierte API aufrufen und lassen sich Optionen wie Pfade oder Quellen setzen und Diagnosemeldungen einholen. Wir wollen im folgenden Beispiel ein einfaches Programm entwickeln, das Java-Quellcode zur Laufzeit schreibt, diesen dann mit der Java Compiler API übersetzt und anschließend über einen eigenen Klassenlader lädt. Das Generieren einer Datei mit dem Quellcode bereitet die wenigsten Schwierigkeiten: Listing 19.1: com/tutego/insel/tools/CompileDemo.java, main() – Teil 1
    File javaSrcFile = new File( "A.java" );
    Writer p = new FileWriter( javaSrcFile );
    p.write( "class A { static { System.out.println(\"Java Compiler API\"); } }" );
    p.close();
Im nächsten Schritt ist die Compiler API gefragt: Listing 19.2: com/tutego/insel/tools/CompileDemo.java, main() – Teil 2
    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    StandardJavaFileManager fileManager = compiler.getStandardFileManager( null, null, null );
    Iterable<? extends JavaFileObject> units;
    units = fileManager.getJavaFileObjectsFromFiles( Arrays.asList( javaSrcFile ) );
    CompilationTask task = compiler.getTask( null, fileManager, null, null, null, units );
    task.call();
    fileManager.close();
Folgende Typen spielen mit: Der ToolProvider gibt mit der statischen Funktion getSystemJavaCompiler() ein Objekt vom Typ JavaCompiler zurück, das Zugang zum Compiler ermöglicht. (Sonst hat der ToolProvider nur noch eine zweite Methode, getSystemToolClassLoader() – es ist also noch Potenzial für eine API der anderen Tools). Vor der Übersetzung muss der Compiler initialisiert werden, etwa mit dem Ursprung der zu übersetzenden Quellen. Dazu wird dem Compiler ein JavaFileManager mitgegeben. Anstatt einen eigenen JavaFileManager zu entwickeln – das werden wir später machen, wenn die Klassen aus Bytefeldern stammen –, lässt sich vom JavaCompiler über getStandardFileManager() ein StandardJavaFileManager holen, der dateiorientiert arbeitet. Über den JavaFileManager weiß der Compiler, wo er die Quellen zu suchen hat und wohin er die Bytecodedateien schreiben soll, doch weiß er nicht, welche Klassen er genau übersetzen soll. Dazu ist jede zu übersetzende Compilationseinheit in ein JavaFileObject zu kapseln. Den Aufbau übernimmt in unserem Fall getJavaFileObjectsFromFiles(), das eine Sammlung von File-Objekten in JavaFileObject-Typen konvertiert. Die Alternative getJavaFileObjectsFromStrings() nimmt statt File-Objekten die Pfadnamen aus Strings. Mit dem JavaFileManager und der Liste der Compilationseinheiten in Form von JavaFileObject-Objekten liefert das JavaCompilerTool-Objekt anschließend mit getTask() ein JavaCompilerTool.CompilationTask-Objekt. Ein Aufruf von call() auf dem CompilationTask startet die Übersetzung. Die Methode getTask() ist besonders lang und enthält viele null-Argumente. null ist immer erlaubt und setzt dann das Standardverhalten. An der Signatur der Methode lassen sich die Parameter besser ablesen:
    CompilationTask getTask( Writer out,
                             JavaFileManager fileManager,
                             DiagnosticListener<? super JavaFileObject> diagnosticListener,
                             Iterable<String> options,
                             Iterable<String> classes,
                             Iterable<? extends JavaFileObject> compilationUnits );
Hinweis
Als viertes Argument von getTask() lassen sich dem Compiler die üblichen Compiler-Optionen übergeben, also Pfade und sonstige Einstellungen:
    Iterable<String> options = Arrays.asList( "-verbose" );

    CompilationTask task = compiler.getTask( null, fileManager, null, options, null, units );
Im letzten Schritt soll ein eigener Klassenlader die Klasse laden und anschließend den Quellcode löschen. Es folgt eine Ausgabe vom Klasseninitialisierer auf dem Bildschirm: Listing 19.3: com/tutego/insel/tools/CompileDemo.java, main() – Teil 3
    URLClassLoader classLoader = new URLClassLoader(
      new URL[] { javaSrcFile.getAbsoluteFile().getParentFile().toURI().toURL() } );
    Class.forName( "A", true, classLoader );    // Java Compiler API

    javaSrcFile.delete();
Hinweis
Der Typ CompilationTask ist methodenarm. Neben der call()-Methode gibt es gerade einmal setLocale() zum Setzen der Sprache für Fehler- und Diagnosemeldungen sowie setProcessors(). Die letzte Methode ermöglicht die Zuweisung von Annotations-Prozessoren. Mit ihnen haben wir uns schon in —Kapitel 18, »Reflection und Annotationen«, beschäftigt. Die JSR-269, »Pluggable Annotation Processing API«, wurde wie die JSR-199, »Java Compiler API«, in Java 6 eingeführt.
### 19.2.2 Fehler-Diagnose Entwicklern ist leidvoll bekannt, dass die Übersetzung nicht immer gut geht. Der erste Hinweis ist die Rückgabe von call(); sie ist false, wenn die Übersetzung fehlschlägt. Des Weiteren bietet die Compiler API zwei Möglichkeiten, einen Statusbericht zu bekommen: Ein DiagnosticListener wird immer genau dann benachrichtigt, wenn der Compiler auf ein Problem stößt. Ein DiagnosticCollector ist ein vordefinierter DiagnosticListener, der alle Meldungen sammelt und später zugänglich macht. Sehen wir uns an, was der DiagnosticCollector an Hinweisen liefert. Listing 19.4: com/tutego/insel/tools/CompileWithDiagnosticsDemo.java, main()
    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    DiagnosticCollector<JavaFileObject> diagnostics =
      new DiagnosticCollector<JavaFileObject>();
    StandardJavaFileManager fileManager = compiler.getStandardFileManager( diagnostics, null,                                                                        null );
    Iterable<? extends JavaFileObject> units = fileManager.getJavaFileObjectsFromFiles(
                                                 Arrays.asList( new File("QQ.java"),
                                                                javaSrcFile ) );
    CompilationTask task = compiler.getTask( null, fileManager, diagnostics, null, null, units );
    boolean success = task.call();
    fileManager.close();

    System.out.println( success ); // false

    for ( Diagnostic<?> diagnostic : diagnostics.getDiagnostics() )
    {
      System.out.printf( "Kind: %s%n", diagnostic.getKind() );
      System.out.printf( "Quelle: %s%n", diagnostic.getSource() );
      System.out.printf( "Code und Nachricht: %s: %s%n", diagnostic.getCode(),
                                                         diagnostic.getMessage( null ) );
      System.out.printf( "Zeile: %s%n", diagnostic.getLineNumber() );
      System.out.printf( "Position/Spalte: %s/%s%n", diagnostic.getPosition(),
                                                     diagnostic.getColumnNumber() );
      System.out.printf( "Startpostion/Endposition: %s/%s%n", diagnostic.getStartPosition(),
                                                              diagnostic.getEndPosition() );
      System.out.println();
    }
Wir bitten den Compiler, eine nicht existierende Datei QQ.java zu übersetzen und folgenden Quellcode:
    class A
    {
    staticccc
    {
    System.outprintln("Java Compiler API")
Die Reaktion vom Compiler sieht so aus:
    false
    Kind: ERROR
    Quelle: QQ.java
    Code und Nachricht: compiler.err.error.reading.file: error: error reading QQ.java; QQ.java (Das System kann die angegebene Datei nicht finden)
    Zeile: –1
    Position/Spalte: –1/-1
    Startpostion/Endposition: –1/-1

    Kind: ERROR
    Quelle: A.java
    Code und Nachricht: compiler.err.expected: A.java:3: <identifier> expected
    Zeile: 3
    Position/Spalte: 19/10
    Startpostion/Endposition: 19/19

    Kind: ERROR
    Quelle: A.java
    Code und Nachricht: compiler.err.expected: A.java:5: <identifier> expected
    Zeile: 5
    Position/Spalte: 39/18
    Startpostion/Endposition: 39/39

    Kind: ERROR
    Quelle: A.java
    Code und Nachricht: compiler.err.illegal.start.of.type: A.java:5: illegal start of type
    Zeile: 5
    Position/Spalte: 40/19
    Startpostion/Endposition: 40/40

    Kind: ERROR
    Quelle: A.java
    Code und Nachricht: compiler.err.premature.eof: A.java:5: reached end of file while
    parsing
    Zeile: 5
    Position/Spalte: 60/39
    Startpostion/Endposition: 60/60
Die Nutzung eines DiagnosticListener ist nicht viel anders. Anstatt den DiagnosticCollector als Parameter zu übergeben, wird eine DiagnosticListener-Implementierung übergeben. So kann sie aussehen: Listing 19.5: com/tutego/insel/tools/ SimpleDiagnosticListener.java, SimpleDiagnosticListener
    public class SimpleDiagnosticListener implements DiagnosticListener<JavaFileObject>
    {
      @Override
      public void report( Diagnostic<? extends JavaFileObject> diagnostic )
      {
        // ...
      }
    }
### 19.2.3 Eine im String angegebene Compiliationseinheit übersetzen Steht der Quellcode einer Compilationseinheit in einer Zeichenkette, so muss das Programm diese nicht erst in eine temporäre Datei schreiben. Zwar sieht die Compiler-API nicht direkt eine einfache Methode zum Übersetzen von Klassen aus Strings vor, doch viel an Infrastruktur ist schon vorhanden, sodass diese Funktionalität schnell realisiert ist. Der erste Schritt ist die Entwicklung einer Unterklasse von JavaFileObject, die wir StringJavaFileObject nennen wollen. Das können wir dann wieder problemlos in eine Datenstruktur setzen und an getTask() übergeben. Die StringJavaFileObject-Klasse muss lediglich den Quellcode der Compilationseinheit über getCharContent() verfügbar machen. Listing 19.6: com/tutego/insel/tools/StringJavaFileObject, StringJavaFileObject
    public class StringJavaFileObject extends SimpleJavaFileObject
    {
      private final CharSequence code;

      public StringJavaFileObject( String name, CharSequence code )
      {
        super( URI.create( "string:///" + name.replace( '.', '/' ) + Kind.SOURCE.extension ),
                           Kind.SOURCE );
        this.code = code;
      }

      @Override
      public CharSequence getCharContent( boolean ignoreEncodingErrors )
      {
        return code;
      }
    }
Der Konstruktor erwartet den Namen der zu übersetzenden Klasse und ein CharSequence (etwa String, StringBuilder) mit der Compilationseinheit. Der Klassenname wird an die Oberklasse weitergegeben. KIND ist eine in JavaFileObject deklarierte Aufzählung mit SOURCE (für Dateien mit der Endung .java), CLASS (für Klassendateien mit der Endung .class), HTML (für HTML-Dokumente) und OTHER. Um den String mit dem Java-Quellcode in Bytecode zu übersetzen, wird die bekannte Zeile
    Iterable<? extends JavaFileObject> units;
    units = fileManager.getJavaFileObjectsFromFiles( Arrays.asList( javaSrcFile ) );
ersetzt durch:
    Iterable<? extends JavaFileObject> units = Arrays.asList( javaFile );
Das gesamte Programm sieht so aus: Listing 19.7: com/tutego/insel/tools/CompileFromStringDemo, main()
    String src = "class A { static { System.out.println(\"Java Compiler API 2\"); } }";
    StringJavaFileObject javaFile = new StringJavaFileObject( "A", src );

    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    StandardJavaFileManager fileManager = compiler.getStandardFileManager( null, null, null );
    Iterable<? extends JavaFileObject> units = Arrays.asList( javaFile );
    CompilationTask task = compiler.getTask( null, fileManager, null, null, null, units );
    task.call();
    fileManager.close();

    URLClassLoader classLoader = new URLClassLoader( new URL[] { new File(".").getAbsoluteFile().toURI().toURL() } );
    Class.forName( "A", true, classLoader );    // Java Compiler API 2
### 19.2.4 Wenn Quelle und Ziel der Speicher sind Stammt nur der Quellcode aus dem Hauptspeicher, ist wenig Programmieraufwand nötig. Doch bisher legte der Compiler die Klassendateien immer auf dem Laufwerk in einen Ausgabeverzeichnis ab. Soll er nicht nur die Quellen aus dem Speicher beziehen, sondern ebenso die generierten Bytecode-Dateien im Speicher ablegen, ist mehr Aufwand nötig. Die Quellen (Units) gibt weiterhin eine Sammlung von JavaFileObject an, sodass hier nichts zu ändern ist. Jedoch ist ein neuer JavaFileManager nötig. Das folgende Programm übersetzt eine Klassendeklaration aus einer Zeichenkette und schreibt in ein spezielles »Dateisystem«. Die gegenüber der letzten Version geänderten Stellen sind fett hervorgehoben: Listing 19.8: com/tutego/insel/tools/CompileToMemoryDemo, main()
    String src = "public class A { static { System.out.println(\"Java Compiler API 3\"); } }";

    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    MemClassLoader classLoader = new MemClassLoader();
    JavaFileManager fileManager = new MemJavaFileManager( compiler, classLoader );
    JavaFileObject javaFile = new StringJavaFileObject( "A", src );
    Collection<JavaFileObject> units = Collections.singleton( javaFile );
    CompilationTask task = compiler.getTask( null, fileManager, null, null, null, units );
    task.call();
    fileManager.close();

    Class.forName( "A", true, classLoader ).newInstance(); // Java Compiler API 3
#### Die nötigen Typen im Überblick Zwei neue Klassen, MemJavaFileManager und MemClassLoader, sind im Beispiel sichtbar. Eine weitere interne Klasse MemJavaFileObject muss ebenfalls implementiert werden: MemJavaFileManager: Was im Text »Dateimanager« für den Compiler genannt wurde, ist natürlich ein spezieller JavaFileManager. Der Compiler ruft immer dann die Methode getJavaFileForOutput() auf dem speziellen JavaFileManager auf, wenn er eine Datei ablegen möchte. Es ist nun Aufgabe von MemJavaFileManager und der Methode MemJavaFileObject, dem Compiler ein JavaFileObject zu geben, das die Bytes der Klassendatei speichert. MemJavaFileObject ist genau so ein JavaFileObject, das der MemJavaFileManager bei getJavaFileForOutput() liefert. Der Compiler ruft die von MemJavaFileObject überschriebene Methode openOutputStream() auf und bekommt so die Möglichkeit, die Klassendatei herauszuschreiben. An der Stelle würden wir auch ansetzen, wenn der Compiler einen anderen Speicherort wählen sollte, etwa eine Datenbank, oder den Bytecode gleich verschlüsselt ablegen soll. MemClassLoader ist ein Klassenlader und das letzte Teilchen. Der erzeugte Bytecode pro Klasse oder Schnittstelle steht ja in einem MemJavaFileObject-Objekt. Der eigene Klassenlader macht diese erzeugen Bytefelder für das Class.forName() zugänglich. Zu den drei Klassen: Listing 19.9: com/tutego/insel/tools/MemJavaFileManager.java
    package com.tutego.insel.tools;

    import javax.tools.*;
    import javax.tools.JavaFileObject.Kind;

    public class MemJavaFileManager extends
      ForwardingJavaFileManager<StandardJavaFileManager>
    {
      private final MemClassLoader classLoader;

      public MemJavaFileManager( JavaCompiler compiler, MemClassLoader classLoader )
      {
        super( compiler.getStandardFileManager( null, null, null ) );

        this.classLoader = classLoader;
      }

      @Override
      public JavaFileObject getJavaFileForOutput( Location location,
                                                  String className,
                                                  Kind kind,
                                                  FileObject sibling )
      {
        MemJavaFileObject fileObject = new MemJavaFileObject( className );
        classLoader.addClassFile( fileObject );
        return fileObject;
      }
    }
Listing 19.10: com/tutego/insel/tools/MemJavaFileObject.java
    package com.tutego.insel.tools;

    import java.io.*;
    import java.net.*;
    import javax.tools.*;

    class MemJavaFileObject extends SimpleJavaFileObject
    {
      private final ByteArrayOutputStream baos = new ByteArrayOutputStream( 8192 );
      private final String className;

      MemJavaFileObject( String className )
      {
        super( URI.create( "string:///" + className.replace( '.', '/' ) + Kind.CLASS.extension ),
               Kind.CLASS );
        this.className = className;
      }

      String getClassName()
      {
        return className;
      }

      byte[] getClassBytes()
      {
        return baos.toByteArray();
      }

      @Override public OutputStream openOutputStream()
      {
        return baos;
      }
    }
Listing 19.11: com/tutego/insel/tools/MemClassLoader.java
    package com.tutego.insel.tools;

    import java.util.*;

    public class MemClassLoader extends ClassLoader
    {
      private final Map<String, MemJavaFileObject> classFiles =
        new HashMap<String, MemJavaFileObject>();

      public MemClassLoader()
      {
        super( ClassLoader.getSystemClassLoader() );
      }

      public void addClassFile( MemJavaFileObject memJavaFileObject )
      {
        classFiles.put( memJavaFileObject.getClassName(), memJavaFileObject );
      }

      @Override
      protected Class<?> findClass( String name ) throws ClassNotFoundException
      {
        MemJavaFileObject fileObject = classFiles.get( name );

        if ( fileObject != null )
        {
          byte[] bytes = fileObject.getClassBytes();
          return defineClass( name, bytes, 0, bytes.length );
        }

        return super.findClass( name );
      }
    }
### Ihr Kommentar Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen. >> Zum Feedback-Formular
<< zurück | <top> | vor >>
Zum Katalog
Java 7 – Mehr als eine Insel Jetzt bestellen
Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen? Ihre Meinung
Buchempfehlungen
| Java und XML
| Einstieg in Eclipse 3.7
| Android 3
| NetBeans Platform 7
| Java ist auch eine Insel
| Apps entwickeln für Android 4
| Java 7
Shopping
Versandkostenfrei bestellen in Deutschland und Österreich Info

Copyright © Galileo Press 2012
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

[Galileo Computing]
Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de