Серёжа Пономарёв aka stokito

I'm Java & Grails developer, coach and founder of IT community #kranonit in my native city.

Динамическая компиляция Java

на Февраль 14, 2013

Чтобы получить выполняемый Java *.class файл нам нужно написать текст на Яве и затем скомпилировать его компилятором javac.
А что делать если нам хочется динамически компилировать их?
Можно дёргать компилятор как системную команду:

Runtime.getRuntime().exec("cmd javac"); 

Но это конечно же неудобно, да ещё и проблемы с парсингом результатов компилятора.
В шестой джаве нам немного облегчили задачу добавив Java Compiler API (JSR 199). В моём блоге уже есть перевод крутой статьи в котором плотно рассматривается это API, но я хочу сделать отдельный обзор.
Сам компилятор явы написан на плюсах, поэтому полный API к нему никто не будет писать, так что по сути Java Compiler API просто обвёртка над javac со всеми вытекающими.
Например нельзя сперва построить AST дерево класса а потом скомпилировать его — мы можем передавать только уже готовые исходники явы текстом.
Вообщем я из разных источников собрал простой проект который динамически компилирует HelloWorld, подгружает этот класс и вызывает.
В коде есть комментарии, их конечно не достаточно, но не пересказывать же JavaDoc?
Проект есть на GitHub, но по сути там один класс:

package name.stokito.javaDynamicCompiler;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.Locale;

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.StandardJavaFileManager;
import javax.tools.ToolProvider;

/** A test class to test dynamic compilation API. */
public class CompilerAPITest {

    private static final String CLASS_NAME = "name.stokito.javaDynamicCompiler.HelloWorld";

    /** Java source code to be compiled dynamically */
    private static final String SOURCE_CODE = "package name.stokito.javaDynamicCompiler;" +
            "public class HelloWorld {" +
            "public static void main (String args[]){" +
            "System.out.println (\"Hello, dynamic compilation world!\");" +
            "}" +
            "}";

    /** Does the required object initialization and compilation. */
    public void doCompilation(File outputDir) throws IOException, ClassNotFoundException {
        // Creating dynamic java source code file object
        SimpleJavaFileObject fileObject = new DynamicJavaSourceCodeObject(CLASS_NAME, SOURCE_CODE);
        JavaFileObject javaFileObjects[] = new JavaFileObject[]{fileObject};
        // Instantiating the java compiler
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        /**
         * Retrieving the standard file manager from compiler object, which is used to provide
         * basic building block for customizing how a compiler reads and writes to files.
         *
         * The same file manager can be reopened for another compiler task.
         * Thus we reduce the overhead of scanning through file system and jar files each time
         */
        StandardJavaFileManager stdFileManager = compiler.getStandardFileManager(null, Locale.getDefault(), null);
        // Prepare a list of compilation units (java source code file objects) to input to compilation task
        Iterable compilationUnits = Arrays.asList(javaFileObjects);
        // Prepare any compilation options to be used during compilation
        // In this example, we are asking the compiler to place the output files under bin folder.
        String[] compileOptions = new String[]{"-d", outputDir.getAbsolutePath()};
        Iterable compilationOptions = Arrays.asList(compileOptions);
        // Create a diagnostic controller, which holds the compilation problems
        DiagnosticCollector diagnostics = new DiagnosticCollector();
        // Create a compilation task from compiler by passing in the required input objects prepared above
        CompilationTask compilerTask = compiler.getTask(null, stdFileManager, diagnostics, compilationOptions, null, compilationUnits);
        // Perform the compilation by calling the call method on compilerTask object.
        boolean status = compilerTask.call();
        if (!status) { // If compilation error occurs
            // Iterate through each compilation problem and print it
            for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
                System.err.format("Error on line %d in %s", diagnostic.getLineNumber(), diagnostic);
            }
        }
        stdFileManager.close(); // Close the file manager
    }

    public static void main(String args[]) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        File outputDir = new File("target" + File.separator + "bin");
        outputDir.mkdir();
        new CompilerAPITest().doCompilation(outputDir);
        invoke(outputDir);
    }

    private static void invoke(File outputDir) throws MalformedURLException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        URL[] urls = new URL[]{outputDir.toURI().toURL()};
        URLClassLoader ucl = new URLClassLoader(urls);
        Class clazz = ucl.loadClass(CLASS_NAME);
        Method main = clazz.getMethod("main", String[].class);
        String[] mainArgs = new String[]{};
        System.out.format("invoking %s.main()%n", clazz.getName());
        main.invoke(null, (Object) mainArgs);
    }

}

/**
 * Creates a dynamic source code file object
 * <p />* This is an example of how we can prepare a dynamic java source code for compilation.
 * This class reads the java code from a string and prepares a JavaFileObject
 */
class DynamicJavaSourceCodeObject extends SimpleJavaFileObject {
    private String qualifiedName;
    private String sourceCode;

    /**
     * Converts the name to an URI, as that is the format expected by JavaFileObject
     *
     * @param name fully qualified name given to the class file
     * @param code the source code string
     */
    protected DynamicJavaSourceCodeObject(String name, String code) {
        super(URI.create("string:///" + name.replaceAll("\\.", "/") + Kind.SOURCE.extension), Kind.SOURCE);
        this.qualifiedName = name;
        this.sourceCode = code;
    }

    @Override
    public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
        return sourceCode;
    }

    public String getQualifiedName() {
        return qualifiedName;
    }

    public void setQualifiedName(String qualifiedName) {
        this.qualifiedName = qualifiedName;
    }

    public String getSourceCode() {
        return sourceCode;
    }

    public void setSourceCode(String sourceCode) {
        this.sourceCode = sourceCode;
    }
}

Вот так вот. Сильно не разгонишься :(
Ещё несколько ссылок по теме, может сэкономит вам гуглёж:
JavaDoc
Отрывок из книги
Dynamic in-memory compilation
The Java 6.0 Compiler API
Generating Java classes dynamically through Java compiler API
Интервью с создателем JSR 199
The story of javac AST Visualization on NetBeans Platform

Что делать если таки очень хочется компилить по-человечки?
Решения есть, попробую и отпишусь о них позже

About these ads

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Вы комментируете при помощи своего аккаунта WordPress.com. Выход / Изменить )

Фотография Twitter

Вы комментируете при помощи своего аккаунта Twitter. Выход / Изменить )

Фотография Facebook

Вы комментируете при помощи своего аккаунта Facebook. Выход / Изменить )

Google+ photo

Вы комментируете при помощи своего аккаунта Google+. Выход / Изменить )

Connecting to %s

Отслеживать

Get every new post delivered to your Inbox.

Join 385 other followers

%d bloggers like this: