web-dev-qa-db-fra.com

Compiler le code entièrement en mémoire avec javax.tools.JavaCompiler

J'utilise le JavaCompiler du paquet javax.tools (JDK 1.7) pour compiler des choses à la volée, comme ceci:

compiler.run(null, null, "-cp", paths, "path/to/my/file.Java");

Cela fonctionne, mais j'aimerais tout faire en mémoire (par exemple, passer une chaîne avec le code, pas le fichier source, et récupérer le code d'octet, pas un fichier .class). J'ai constaté que l'extension des paramètres InputStream et OutputStream ne sert à rien car c'est probablement la même chose que dans la console. Connaissez-vous un moyen de faire fonctionner la méthode run de cette manière? Ou connaissez-vous un moyen confirmé de le faire avec la méthode getTask()? (L'extension du FileManager semble facile mais pas si facile :)

39
Sandman

J'ai exécuté le code ci-dessus sous Mac OS Java 7. Aucun d'entre eux ne fonctionne. Donc j'ai écrit un https://github.com/trung/InMemoryJavaCompiler

StringBuffer sourceCode = new StringBuffer();
sourceCode.append("package org.mdkt;\n");
sourceCode.append("public class HelloClass {\n");
sourceCode.append("   public String hello() { return \"hello\"; }");
sourceCode.append("}");

Class<?> helloClass = InMemoryJavaCompiler.compile("org.mdkt.HelloClass", sourceCode.toString());
30
erolagnab

Je pense que ceci peut être utile car il montre fondamentalement comment compiler le code source Java à partir de la mémoire (la chaîne est située dans la classe). 

Il utilise les variables PrinterWriter et StringWriter pour écrire la source dans une mémoire Stringin, puis la classe JavaCompiler (à partir de JDK 6) pour compiler et exécuter le programme:

import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.ToolProvider;
import Java.io.File;
import Java.io.IOException;
import Java.io.PrintWriter;
import Java.io.StringWriter;
import Java.lang.reflect.InvocationTargetException;
import Java.net.URI;
import Java.net.URL;
import Java.net.URLClassLoader;
import Java.util.Arrays;


public class CompileSourceInMemory {
  public static void main(String args[]) throws IOException {
    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();

    StringWriter writer = new StringWriter();
    PrintWriter out = new PrintWriter(writer);
    out.println("public class HelloWorld {");
    out.println("  public static void main(String args[]) {");
    out.println("    System.out.println(\"This is in another Java file\");");    
    out.println("  }");
    out.println("}");
    out.close();
    JavaFileObject file = new JavaSourceFromString("HelloWorld", writer.toString());

    Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(file);
    CompilationTask task = compiler.getTask(null, null, diagnostics, null, null, compilationUnits);

    boolean success = task.call();
    for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
      System.out.println(diagnostic.getCode());
      System.out.println(diagnostic.getKind());
      System.out.println(diagnostic.getPosition());
      System.out.println(diagnostic.getStartPosition());
      System.out.println(diagnostic.getEndPosition());
      System.out.println(diagnostic.getSource());
      System.out.println(diagnostic.getMessage(null));

    }
    System.out.println("Success: " + success);

    if (success) {
      try {

          URLClassLoader classLoader = URLClassLoader.newInstance(new URL[] { new File("").toURI().toURL() });
          Class.forName("HelloWorld", true, classLoader).getDeclaredMethod("main", new Class[] { String[].class }).invoke(null, new Object[] { null });

      } catch (ClassNotFoundException e) {
        System.err.println("Class not found: " + e);
      } catch (NoSuchMethodException e) {
        System.err.println("No such method: " + e);
      } catch (IllegalAccessException e) {
        System.err.println("Illegal access: " + e);
      } catch (InvocationTargetException e) {
        System.err.println("Invocation target: " + e);
      }
    }
  }
}

class JavaSourceFromString extends SimpleJavaFileObject {
  final String code;

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

  @Override
  public CharSequence getCharContent(boolean ignoreEncodingErrors) {
    return code;
  }
}

Si vous consultez le lien de référence, vous trouverez également quelques exemples supplémentaires.

_/ Référence:

30
David Kroukamp

J'ai écrit une bibliothèque pour le faire il y a quelques années. Il faut une chaîne pouvant contenir des classes imbriquées, les compiler et éventuellement les charger dans le chargeur de classes actuel (vous n'avez donc pas besoin d'un chargeur de classes supplémentaire). Si la machine virtuelle Java s'exécute en mode débogage, le code généré sera écrit dans un fichier. afin que vous puissiez parcourir votre code généré.

http://vanillajava.blogspot.co.uk/2010_11_01_archive.html


Pour paraphraser l'exemple de erolagnab vous pouvez faire

StringBuilder sourceCode = new StringBuilder();
sourceCode.append("package org.mdkt;\n")
          .append("public class HelloClass {\n")
          .append("   public String hello() { return \"hello\"; }")
          .append("}");

Class<?> helloClass = CACHED_COMPILER.compile("org.mdkt.HelloClass",
                                              sourceCode.toString());

Mise à jour, la source est disponible ici https://github.com/OpenHFT/Java-Runtime-Compiler

Et vous pouvez obtenir la dernière version via maven http://search.maven.org/#browse%7C842970587


Un exemple plus long.

// this writes the file to disk only when debugging is enabled.
CachedCompiler cc = CompilerUtils.DEBUGGING ?
        new CachedCompiler(new File(parent, "src/test/Java"), new File(parent, "target/compiled")) :
        CompilerUtils.CACHED_COMPILER;

String text = "generated test " + new Date();
Class fooBarTeeClass = cc.loadFromJava("eg.FooBarTee", "package eg;\n" +
    '\n' +
    "import eg.components.BarImpl;\n" +
    "import eg.components.TeeImpl;\n" +
    "import eg.components.Foo;\n" +
    '\n' +
    "public class FooBarTee{\n" +
    "    public final String name;\n" +
    "    public final TeeImpl tee;\n" +
    "    public final BarImpl bar;\n" +
    "    public final BarImpl copy;\n" +
    "    public final Foo foo;\n" +
    '\n' +
    "    public FooBarTee(String name) {\n" +
    "        // when viewing this file, ensure it is synchronised with the copy on disk.\n" +
    "        System.out.println(\"" + text + "\");\n" +
    "        this.name = name;\n" +
    '\n' +
    "        tee = new TeeImpl(\"test\");\n" +
    '\n' +
    "        bar = new BarImpl(tee, 55);\n" +
    '\n' +
    "        copy = new BarImpl(tee, 555);\n" +
    '\n' +
    "        // you should see the current date here after synchronisation.\n" +
    "        foo = new Foo(bar, copy, \"" + text + "\", 5);\n" +
    "    }\n" +
    '\n' +
    "    public void start() {\n" +
    "    }\n" +
    '\n' +
    "    public void stop() {\n" +
    "    }\n" +
    '\n' +
    "    public void close() {\n" +
    "        stop();\n" +
    '\n' +
    "    }\n" +
    "}\n");

// add a debug break point here and step into this method.
FooBarTee fooBarTee = new FooBarTee("test foo bar tee");
Foo foo = fooBarTee.foo;
assertNotNull(foo);
assertEquals(text, foo.s);
7
Peter Lawrey

Je voulais:

  • compilation en mémoire d'un fichier Java (utile pour Java en tant que langage de script)
  • Aucune dépendance supplémentaire (facile à installer)
  • Mise en œuvre dans un nombre de fichiers aussi faible que possible (facile à intégrer dans un projet)

Vous pouvez l'essayer d'abord ici: http://ideone.com/cu1GhE#view_edit_box

Le code suivant est basé sur Rekha Kumari code: 

Main.Java

package com.mycompany.Java;

//import org.slf4j.Logger;
//import org.slf4j.LoggerFactory;

import Java.io.PrintWriter;
import Java.io.StringWriter;
import Java.util.ArrayList;
import Java.util.Arrays;
import Java.util.List;

public class Main {

    //private static final Logger logger = LoggerFactory.getLogger(Main.class);

    public static void main(String[] args) {    
        try {
            StringWriter writer = new StringWriter();
            PrintWriter out = new PrintWriter(writer);
            out.println("package com.mycompany.script;");
            out.println("");
            out.println("public class HelloWorld {");
            out.println("  public static void main(String args[]) {");
            out.println("    System.out.println(\"This is in another Java file\");");
            out.println("  }");
            out.println("}");
            out.close();

            String fullName = "com.mycompany.script.HelloWorld";
            String src = writer.toString();

            DynamicCompiler uCompiler = new DynamicCompiler(fullName, src);
            uCompiler.compile();
            uCompiler.run();

        } catch (Exception e) {
            //logger.error("Exception:", e);
            System.out.print("Exception");
        }
    }    
}

DynamicCompiler.Java

package com.mycompany.Java;

//import org.slf4j.Logger;
//import org.slf4j.LoggerFactory;

import javax.tools.*;
import Java.io.ByteArrayOutputStream;
import Java.io.IOException;
import Java.io.OutputStream;
import Java.lang.reflect.InvocationTargetException;
import Java.net.URI;
import Java.security.SecureClassLoader;
import Java.util.ArrayList;
import Java.util.List;

// Based on: http://javapracs.blogspot.cz/2011/06/dynamic-in-memory-compilation-using.html
public class DynamicCompiler {
    //private static final Logger logger = LoggerFactory.getLogger(DynamicCompiler.class);

    private JavaFileManager fileManager;
    private String fullName;
    private String sourceCode;

    public DynamicCompiler(String fullName, String srcCode) {
        this.fullName = fullName;
        this.sourceCode = srcCode;
        this.fileManager = initFileManager();
    }

    public JavaFileManager initFileManager() {
        if (fileManager != null)
            return fileManager;
        else {
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            fileManager = new
                    ClassFileManager(compiler
                    .getStandardFileManager(null, null, null));
            return fileManager;
        }
    }

    public void compile() {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();    
        List<JavaFileObject> files = new ArrayList<>();
        files.add(new CharSequenceJavaFileObject(fullName, sourceCode));

        compiler.getTask(
                null,
                fileManager,
                null,
                null,
                null,
                files
        ).call();
    }

    public void run() throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        try {
            fileManager
                    .getClassLoader(null)
                    .loadClass(fullName)
                    .getDeclaredMethod("main", new Class[]{String[].class})
                    .invoke(null, new Object[]{null});
        } catch (InvocationTargetException e) {
            System.out.print("InvocationTargetException");
            //logger.error("InvocationTargetException:", e);
        } catch (NoSuchMethodException e) {
            System.out.print("NoSuchMethodException ");
            //logger.error("NoSuchMethodException:", e);
        }
    }

    public class CharSequenceJavaFileObject extends SimpleJavaFileObject {

        /**
         * CharSequence representing the source code to be compiled
         */
        private CharSequence content;

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

        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
            return content;
        }
    }

    public class ClassFileManager extends ForwardingJavaFileManager {
        private JavaClassObject javaClassObject;

        public ClassFileManager(StandardJavaFileManager standardManager) {
            super(standardManager);
        }

        @Override
        public ClassLoader getClassLoader(Location location) {
            return new SecureClassLoader() {
                @Override
                protected Class<?> findClass(String name) throws ClassNotFoundException {
                    byte[] b = javaClassObject.getBytes();
                    return super.defineClass(name, javaClassObject.getBytes(), 0, b.length);
                }
            };
        }

        public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
            this.javaClassObject = new JavaClassObject(className, kind);
            return this.javaClassObject;
        }
    }

    public class JavaClassObject extends SimpleJavaFileObject {
        protected final ByteArrayOutputStream bos =
                new ByteArrayOutputStream();

        public JavaClassObject(String name, Kind kind) {
            super(URI.create("string:///" + name.replace('.', '/')
                    + kind.extension), kind);
        }

        public byte[] getBytes() {
            return bos.toByteArray();
        }

        @Override
        public OutputStream openOutputStream() throws IOException {
            return bos;
        }
    }
}
2
Martin Vseticka

J'aimerais vous présenter ma solution qui fonctionne bien en production.

Voici les trois fichiers de code source.

MemoryJavaCompiler.Java

package me.soulmachine.compiler;

import Java.io.IOException;
import Java.io.PrintWriter;
import Java.io.Writer;
import Java.lang.reflect.Method;
import Java.util.ArrayList;
import Java.util.List;
import Java.util.Map;

import javax.tools.*;

/**
 * Simple interface to Java compiler using JSR 199 Compiler API.
 */
public class MemoryJavaCompiler {
    private javax.tools.JavaCompiler tool;
    private StandardJavaFileManager stdManager;

    public MemoryJavaCompiler() {
        tool = ToolProvider.getSystemJavaCompiler();
        if (tool == null) {
            throw new RuntimeException("Could not get Java compiler. Please, ensure that JDK is used instead of JRE.");
        }
        stdManager = tool.getStandardFileManager(null, null, null);
    }

    /**
     * Compile a single static method.
     */
    public Method compileStaticMethod(final String methodName, final String className,
        final String source)
        throws ClassNotFoundException {
        final Map<String, byte[]> classBytes = compile(className + ".Java", source);
        final MemoryClassLoader classLoader = new MemoryClassLoader(classBytes);
        final Class clazz = classLoader.loadClass(className);
        final Method[] methods = clazz.getDeclaredMethods();
        for (final Method method : methods) {
            if (method.getName().equals(methodName)) {
                if (!method.isAccessible()) method.setAccessible(true);
                return method;
            }
        }
        throw new NoSuchMethodError(methodName);
    }


    public Map<String, byte[]> compile(String fileName, String source) {
        return compile(fileName, source, new PrintWriter(System.err), null, null);
    }


    /**
     * compile given String source and return bytecodes as a Map.
     *
     * @param fileName source fileName to be used for error messages etc.
     * @param source Java source as String
     * @param err error writer where diagnostic messages are written
     * @param sourcePath location of additional .Java source files
     * @param classPath location of additional .class files
     */
    private Map<String, byte[]> compile(String fileName, String source,
        Writer err, String sourcePath, String classPath) {
        // to collect errors, warnings etc.
        DiagnosticCollector<JavaFileObject> diagnostics =
            new DiagnosticCollector<JavaFileObject>();

        // create a new memory JavaFileManager
        MemoryJavaFileManager fileManager = new MemoryJavaFileManager(stdManager);

        // prepare the compilation unit
        List<JavaFileObject> compUnits = new ArrayList<JavaFileObject>(1);
        compUnits.add(fileManager.makeStringSource(fileName, source));

        return compile(compUnits, fileManager, err, sourcePath, classPath);
    }

    private Map<String, byte[]> compile(final List<JavaFileObject> compUnits, 
        final MemoryJavaFileManager fileManager,
        Writer err, String sourcePath, String classPath) {
        // to collect errors, warnings etc.
        DiagnosticCollector<JavaFileObject> diagnostics =
            new DiagnosticCollector<JavaFileObject>();

        // javac options
        List<String> options = new ArrayList<String>();
        options.add("-Xlint:all");
        //      options.add("-g:none");
        options.add("-deprecation");
        if (sourcePath != null) {
            options.add("-sourcepath");
            options.add(sourcePath);
        }

        if (classPath != null) {
            options.add("-classpath");
            options.add(classPath);
        }

        // create a compilation task
        javax.tools.JavaCompiler.CompilationTask task =
            tool.getTask(err, fileManager, diagnostics,
                options, null, compUnits);

        if (task.call() == false) {
            PrintWriter perr = new PrintWriter(err);
            for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
                perr.println(diagnostic);
            }
            perr.flush();
            return null;
        }

        Map<String, byte[]> classBytes = fileManager.getClassBytes();
        try {
            fileManager.close();
        } catch (IOException exp) {
        }

        return classBytes;
    }
}

MemoryJavaFileManager.Java

package me.soulmachine.compiler;


import Java.io.ByteArrayOutputStream;
import Java.io.File;
import Java.io.FilterOutputStream;
import Java.io.IOException;
import Java.io.OutputStream;
import Java.net.URI;
import Java.nio.CharBuffer;
import Java.util.HashMap;
import Java.util.Map;

import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.SimpleJavaFileObject;

/**
 * JavaFileManager that keeps compiled .class bytes in memory.
 */
@SuppressWarnings("unchecked")
final class MemoryJavaFileManager extends ForwardingJavaFileManager {

    /** Java source file extension. */
    private final static String EXT = ".Java";

    private Map<String, byte[]> classBytes;

    public MemoryJavaFileManager(JavaFileManager fileManager) {
        super(fileManager);
        classBytes = new HashMap<>();
    }

    public Map<String, byte[]> getClassBytes() {
        return classBytes;
    }

    public void close() throws IOException {
        classBytes = null;
    }

    public void flush() throws IOException {
    }

    /**
     * A file object used to represent Java source coming from a string.
     */
    private static class StringInputBuffer extends SimpleJavaFileObject {
        final String code;

        StringInputBuffer(String fileName, String code) {
            super(toURI(fileName), Kind.SOURCE);
            this.code = code;
        }

        public CharBuffer getCharContent(boolean ignoreEncodingErrors) {
            return CharBuffer.wrap(code);
        }
    }

    /**
     * A file object that stores Java bytecode into the classBytes map.
     */
    private class ClassOutputBuffer extends SimpleJavaFileObject {
        private String name;

        ClassOutputBuffer(String name) {
            super(toURI(name), Kind.CLASS);
            this.name = name;
        }

        public OutputStream openOutputStream() {
            return new FilterOutputStream(new ByteArrayOutputStream()) {
                public void close() throws IOException {
                    out.close();
                    ByteArrayOutputStream bos = (ByteArrayOutputStream)out;
                    classBytes.put(name, bos.toByteArray());
                }
            };
        }
    }

    public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location,
        String className,
        Kind kind,
        FileObject sibling) throws IOException {
        if (kind == Kind.CLASS) {
            return new ClassOutputBuffer(className);
        } else {
            return super.getJavaFileForOutput(location, className, kind, sibling);
        }
    }

    static JavaFileObject makeStringSource(String fileName, String code) {
        return new StringInputBuffer(fileName, code);
    }

    static URI toURI(String name) {
        File file = new File(name);
        if (file.exists()) {
            return file.toURI();
        } else {
            try {
                final StringBuilder newUri = new StringBuilder();
                newUri.append("mfm:///");
                newUri.append(name.replace('.', '/'));
                if(name.endsWith(EXT)) newUri.replace(newUri.length() - EXT.length(), newUri.length(), EXT);
                return URI.create(newUri.toString());
            } catch (Exception exp) {
                return URI.create("mfm:///com/Sun/script/Java/java_source");
            }
        }
    }
}

MemoryClassLoader.Java

package me.soulmachine.compiler;


import Java.io.File;
import Java.net.MalformedURLException;
import Java.net.URL;
import Java.net.URLClassLoader;
import Java.util.ArrayList;
import Java.util.List;
import Java.util.Map;
import Java.util.StringTokenizer;

/**
 * ClassLoader that loads .class bytes from memory.
 */
final class MemoryClassLoader extends URLClassLoader {
    private Map<String, byte[]> classBytes;

    public MemoryClassLoader(Map<String, byte[]> classBytes,
        String classPath, ClassLoader parent) {
        super(toURLs(classPath), parent);
        this.classBytes = classBytes;
    }

    public MemoryClassLoader(Map<String, byte[]> classBytes, String classPath) {
        this(classBytes, classPath, ClassLoader.getSystemClassLoader());
    }

    public MemoryClassLoader(Map<String, byte[]> classBytes) {
        this(classBytes, null, ClassLoader.getSystemClassLoader());
    }

    public Class load(String className) throws ClassNotFoundException {
        return loadClass(className);
    }

    public Iterable<Class> loadAll() throws ClassNotFoundException {
        List<Class> classes = new ArrayList<Class>(classBytes.size());
        for (String name : classBytes.keySet()) {
            classes.add(loadClass(name));
        }
        return classes;
    }

    protected Class findClass(String className) throws ClassNotFoundException {
        byte[] buf = classBytes.get(className);
        if (buf != null) {
            // clear the bytes in map -- we don't need it anymore
            classBytes.put(className, null);
            return defineClass(className, buf, 0, buf.length);
        } else {
            return super.findClass(className);
        }
    }

    private static URL[] toURLs(String classPath) {
        if (classPath == null) {
            return new URL[0];
        }

        List<URL> list = new ArrayList<URL>();
        StringTokenizer st = new StringTokenizer(classPath, File.pathSeparator);
        while (st.hasMoreTokens()) {
            String token = st.nextToken();
            File file = new File(token);
            if (file.exists()) {
                try {
                    list.add(file.toURI().toURL());
                } catch (MalformedURLException mue) {}
            } else {
                try {
                    list.add(new URL(token));
                } catch (MalformedURLException mue) {}
            }
        }
        URL[] res = new URL[list.size()];
        list.toArray(res);
        return res;
    }
}

Explications:

  1. Afin de représenter un fichier source Java en mémoire au lieu d'un disque, j'ai défini une classe StringInputBuffer dans le MemoryJavaFileManager.Java.
  2. Pour enregistrer les fichiers .class compilés en mémoire, j'ai implémenté une classe MemoryJavaFileManager. L'idée principale est de remplacer la fonction getJavaFileForOutput() pour stocker des codes octets dans une carte.
  3. Pour charger les codes octets en mémoire, je dois implémenter un chargeur de classe personnalisé MemoryClassLoader, qui lit les codes octets dans la carte et les transforme en classes.

Voici un test uni.

package me.soulmachine.compiler;


import org.junit.Test;

import Java.io.IOException;
import Java.lang.reflect.InvocationTargetException;
import Java.lang.reflect.Method;
import Java.util.ArrayList;

import static org.junit.Assert.assertEquals;

public class MemoryJavaCompilerTest {
    private final static MemoryJavaCompiler compiler = new MemoryJavaCompiler();

    @Test public void compileStaticMethodTest()
        throws ClassNotFoundException, InvocationTargetException, IllegalAccessException {
        final String source = "public final class Solution {\n"
            + "public static String greeting(String name) {\n"
            + "\treturn \"Hello \" + name;\n" + "}\n}\n";
        final Method greeting = compiler.compileStaticMethod("greeting", "Solution", source);
        final Object result = greeting.invoke(null, "soulmachine");
        assertEquals("Hello soulmachine", result.toString());
    }
}

Référence

  1. JavaCompiler.Java de Cloudera Morphlines
  2. Comment créer un objet à partir d'une chaîne en Java (comment évaluer une chaîne)?
  3. InMemoryJavaCompiler
  4. Compilateur Java Runtime
  5. [的 Java - 废话 JavaCompilerAPI 中文 指南]
  6. Compiler et exécuter le code source Java en mémoire .
0
soulmachine