2016-12-05 1 views
1

Wir haben die Möglichkeit, dynamisch Java-Code dynamisch zu kompilieren. Ich weiß mindestens Java-Runtime-Compiler und InMemoryJavaCompilerDynamisch kompilieren Sie Java-Code mit Abhängigkeiten von Klassen, die von einem bestimmten Klassenlader geladen wurden

Aber scheint sie können Klasse nicht kompilieren, die von einer bestimmten Klasse von bestimmten Classloader abhängt.

Ist es möglich, Java-Code dynamisch zu kompilieren, der von Klassen abhängt, die nur in bestimmten Klassenladern verfügbar sind? Lassen Sie uns sagen:

ClassLoader classloader = ... // only this CL can load class 'com.External' 
String source = "public class MyClass extends com.External {}"; 
Class<?> compiled = DesiredDynamicCompiler.compile("MyClass", source, classloader); 
// last argument is like an information to compiler where to search all dependencies 

mehr Einblick.Kanäle: Ich würde in Java tun, was genau GroovyClassLoader in groovy tun können:

GroovyClassLoader groovyClassLoader = new GroovyClassLoader(classLoader); 
Class<?> parsedClass = groovyClassLoader.parseClass("some source"); 

können Dieser Code Klasse analysieren, die auf Klassen abhängt nur im angegebenen Classloader.

+0

Sie können sich die [Java Compiler API] (http://docs.oracle.com/javase/8/docs/api/javax/tools/JavaCompiler.html) ansehen, die intern von beiden verwendet wird "Compiler", die Sie vorgestellt haben. Das sind nur ein paar Codezeilen, die um Aufrufe der JavaAPI gewickelt sind (noch deutlicher sichtbar im zweiten verknüpften Repo). – Paul

+0

Mögliche Duplikate von [Wie kompilieren und laden Sie externe Java-Klassen dynamisch?] (Https: // stackoverflow.com/questions/21544446/How-Do-Sie-dynamisch-kompilieren-und-laden-externe-Java-Klassen) – ldmtwo

Antwort

1

Sie sollten alle Abhängigkeiten von Ihrem Klassenpfad haben. Die Tools, auf die Sie verwiesen haben, verwenden die Java Compiler-API trotzdem unter dem Cover.

Es interagiert nicht mit Klassen in der aktuellen Speicher des JVM, es sucht nur nach Abhängigkeiten in dem Classpath.

Sie können durch CompilerUtils gehen ->com.sun.tools.javac.api.JavacTool -> weiter, um ein Gefühl dafür zu bekommen, was dort passiert.

Eine Sache, die Sie versuchen können, ist, dass Ihre dynamisch kompilierten Abhängigkeiten an der richtigen Stelle im Klassenpfad als .class-Dateien abgelegt werden, damit Ihr Kompilierungsprozess sie aufnimmt.

+0

Ja, das war die Option, die ich in Erwägung gezogen. So etwas wie URLs vom URL-Klassenlader abrufen und sie verwenden, um diesen Java-Code dynamisch zu kompilieren. Ich habe gehofft, dass es eine andere Lösung geben wird. – shidzo

1

Es gibt keine Möglichkeit, ClassLoader als Referenz zu verwenden, es sei denn, es ist in der Lage, die Klassenbytes seiner definierten Klassen bereitzustellen. Das heißt, wenn Sie eine Class Instanz haben, die eine Klasse der obersten Ebene darstellt, können Sie classInstance.getResourceAsStream(classInstance.getSimpleName()+".class") verwenden, um zu versuchen, die Klassenbytes in die Hände zu bekommen. Wenn Sie Zugriff auf die Bytes haben, aus denen die dynamische Klasse besteht, können Sie sie dem Java-Compiler über eine -Implementierung zur Verfügung stellen.

Die Compiler-API ist Teil der Standard-API und benötigt keine Bibliotheken von Drittanbietern. Der folgende Code zeigt dies durch eine Testklasse zuerst kompiliert, dann die notwendige Umgebung Einstellung auf der Klasse eine zweite Klasse zu kompilieren, abhängig nur im vorherigen Schritt erstellt:

// customize these, if you want, null triggers default behavior 
DiagnosticListener<JavaFileObject> diagnosticListener = null; 
Locale locale = null; 

// the first class, to be present at runtime only 
String class1 = "package test;\npublic class Class1 {}"; 
JavaCompiler c = ToolProvider.getSystemJavaCompiler(); 
StandardJavaFileManager fm 
    = c.getStandardFileManager(diagnosticListener, locale, Charset.defaultCharset()); 
// define where to store compiled class files - use a temporary directory 
fm.setLocation(StandardLocation.CLASS_OUTPUT, Collections.singleton(
     Files.createTempDirectory("compile-test").toFile())); 
JavaCompiler.CompilationTask task = c.getTask(null, fm, 
    diagnosticListener, Collections.emptySet(), Collections.emptySet(), 
    Collections.singleton(new SimpleJavaFileObject(
     URI.create("string:///Class1.java"), Kind.SOURCE) { 
      public CharSequence getCharContent(boolean ignoreEncodingErrors) { 
       return class1; 
      } 
     })); 
if(task.call()) { 
    FileObject fo = fm.getJavaFileForInput(
      StandardLocation.CLASS_OUTPUT, "test.Class1", Kind.CLASS); 
    // these are the class bytes of the first class 
    byte[] class1bytes = Files.readAllBytes(Paths.get(fo.toUri())); 

    // the actual task: define a class dependent on the first class 
    String class2 = "package test;\npublic class Class2 { Class1 variable; }"; 

    // create a file object representing the dynamic class 
    JavaFileObject jo = new SimpleJavaFileObject(
     URI.create("runtime:///test/Class1.class"), Kind.CLASS) { 
      @Override public InputStream openInputStream() throws IOException { 
       return new ByteArrayInputStream(class1bytes); 
      } 
     }; 

    // and a custom file manager knowing how to locate that class 
    JavaFileManager myFM = new ForwardingJavaFileManager(fm) { 
     @Override 
     public JavaFileObject getJavaFileForInput(
       JavaFileManager.Location location, String className, Kind kind) 
       throws IOException { 
      if(location==StandardLocation.CLASS_PATH&&className.equals("test.Class1")) { 
       return jo; 
      } 
      return super.getJavaFileForInput(location, className, kind); 
     } 

     @Override 
     public boolean hasLocation(JavaFileManager.Location location) { 
      return location==StandardLocation.CLASS_PATH || super.hasLocation(location); 
     } 

     @Override 
     public Iterable list(JavaFileManager.Location location, 
       String packageName, Set kinds, boolean recurse) throws IOException { 
      if(location==StandardLocation.CLASS_PATH 
        && (packageName.equals("test") || recurse&&packageName.isEmpty())) { 
       return Collections.singleton(jo); 
      } 
      return super.list(location, packageName, kinds, recurse); 
     } 

     @Override 
     public String inferBinaryName(
       JavaFileManager.Location location, JavaFileObject file) { 
      if(file==jo) return "test.Class1"; 
      return super.inferBinaryName(location, file); 
     } 
    }; 
    // compile the second class using the custom file manager to locate dependencies 
    task = c.getTask(null, myFM, 
     diagnosticListener, Collections.emptySet(), Collections.emptySet(), 
     Collections.singleton(new SimpleJavaFileObject(
      URI.create("string:///Class2.java"), Kind.SOURCE) { 
       public CharSequence getCharContent(boolean ignoreEncodingErrors) { 
        return class2; 
       } 
      })); 
    if(task.call()) { 
     fo = fm.getJavaFileForInput(
      StandardLocation.CLASS_OUTPUT, "test.Class2", Kind.CLASS); 
     // there we have the compiled second class 
     byte[] class2bytes = Files.readAllBytes(Paths.get(fo.toUri())); 
    } 
} 

Natürlich ist dies nur zur Demonstration des Prinzips . Sie möchten sicher Factory-Methoden für die Dateiobjekte erstellen und Map s für das Erinnern von ihnen usw. verwenden

Es ist auch möglich, das temporäre Verzeichnis durch einen benutzerdefinierten In-Memory-Speicher zu ersetzen. Der entscheidende Punkt bleibt jedoch, dass der Compiler auf die Klassenbytes zugreifen muss. Es wird keine geladenen Laufzeitklassen verwenden.

Verwandte Themen