Freewind @ Thoughtworks scala java javascript dart 工具 编程实践 月结 math python english [comments admin] [feed]

(2012-11-16) 动态编译java源代码

广告: 云梯:翻墙vpn (省10元) 土行孙:科研用户翻墙http proxy (有优惠)

如何在运行时,动态将一个由java源代码组成的字符串编译为Java类?

注意,全部的操作都需要在内存中完成,不能把字符串保存到磁盘上。

题目:

package compiles;

import java.lang.reflect.Method;

/*
* User: Freewind
* Blog: http://freewind.me
* Date: 12-11-16
/
public class CreateClassFromString {

<span class="kwrd">private</span> <span class="kwrd">static</span> final String JAVA_SOURCE = <span class="str">"public class Hello {"</span>
        + <span class="str">"    public void say() {"</span>
        + <span class="str">"        System.out.println(\"world!\");"</span>
        + <span class="str">"    }"</span>
        + <span class="str">"}"</span>;

<span class="kwrd">public</span> <span class="kwrd">static</span> <span class="kwrd">void</span> main(String[] args) throws Exception {
    Class dynaCls = compile(JAVA_SOURCE);
    Method say = dynaCls.getMethod(<span class="str">"say"</span>);
    say.invoke(dynaCls.newInstance());

    <span class="rem">// it will print "world!" on the console</span>
}

<span class="kwrd">private</span> <span class="kwrd">static</span> Class compile(String source) {
    <span class="rem">// compile the source to a class</span>
    <span class="kwrd">return</span> <span class="kwrd">null</span>;
}

}```

解决方案一:使用由Jdk1.6提供的JavaCompiler

在jdk1.6中,提供了一个JavaCompiler接口,它可以方便的将java源代码编译为字节码。

思路如下:

  1. 通过ToolProvider.getSystemJavaCompiler()得到系统javac
  2. 通过compiler.getTask(…)创建一个编译任务,需传入一些参数,如java源文件等
  3. 调用task.call(),进行编译
  4. loadClass(…)载入刚编译出来的类,进行操作

额外操作:

  1. 传入-g参数,使得生成的字节码不会改变变量名等
  2. 两类互调:源代码一同传入getTask(…)
  3. 调用第三方库中的代码。只要将第三方库的jar加入到项目的classpath中

代码如下:

package compiles;

import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.security.SecureClassLoader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class DynaCompTest {

<span class="kwrd">public</span> <span class="kwrd">static</span> <span class="kwrd">void</span> main(String[] args) throws Exception {
    <span class="rem">// Full name of the class that will be compiled.</span>
    <span class="rem">// If class should be in some package, fullName should contain it too (ex. "testpackage.DynaClass")</span>
    String fullName = <span class="str">"DynaClass"</span>;

    <span class="rem">// Here we specify the source code of the class to be compiled</span>
    StringBuilder src = <span class="kwrd">new</span> StringBuilder();
    src.append(<span class="str">"public class DynaClass {\n"</span>);
    src.append(<span class="str">"    public void setName(String name) {}\n"</span>);
    src.append(<span class="str">"    public void invokeAnother() { System.out.println(new DynaClass1()); }\n"</span>);
    src.append(<span class="str">"    public String toString() {\n"</span>);
    src.append(<span class="str">"        return \"Hello, I am \" + this.getClass().getSimpleName();\n"</span>);
    src.append(<span class="str">"    }\n"</span>);
    src.append(<span class="str">"}\n"</span>);

    String fullName1 = <span class="str">"DynaClass1"</span>;
    StringBuilder src1 = <span class="kwrd">new</span> StringBuilder();
    src1.append(<span class="str">"public class DynaClass1 {\n"</span>);
    src1.append(<span class="str">"    public void invokeAnother() { System.out.println(new DynaClass()); }\n"</span>);
    src1.append(<span class="str">"    public String toString() {\n"</span>);
    src1.append(<span class="str">"        org.apache.commons.lang.StringUtils.trim(\" a \");\n"</span>);
    src1.append(<span class="str">"        return \"Hello, I am \" + this.getClass().getSimpleName();\n"</span>);
    src1.append(<span class="str">"    }\n"</span>);
    src1.append(<span class="str">"}\n"</span>);

    <span class="rem">// We get an instance of JavaCompiler. Then we create a file manager (our custom implementation of it)</span>
    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    JavaFileManager fileManager = <span class="kwrd">new</span> ClassFileManager(compiler.getStandardFileManager(<span class="kwrd">null</span>, <span class="kwrd">null</span>, <span class="kwrd">null</span>));

    <span class="rem">// Dynamic compiling requires specifying</span>
    <span class="rem">// a list of "files" to compile. In our case this is a list containing one "file" which is in our case</span>
    <span class="rem">// our own implementation (see details below)</span>
    List<JavaFileObject> jfiles = <span class="kwrd">new</span> ArrayList<JavaFileObject>();
    jfiles.add(<span class="kwrd">new</span> CharSequenceJavaFileObject(fullName, src));
    jfiles.add(<span class="kwrd">new</span> CharSequenceJavaFileObject(fullName1, src1));

    <span class="rem">// We specify a task to the compiler. Compiler should use our file manager and our list of "files".</span>
    <span class="rem">// Then we run the compilation with call()</span>
    <span class="kwrd">if</span> (compiler.getTask(<span class="kwrd">null</span>, fileManager, <span class="kwrd">null</span>, <span class="kwrd">null</span>, <span class="kwrd">null</span>, jfiles).call()) {
        <span class="rem">// Creating an instance of our compiled class and running its toString() method</span>
        Object instance = fileManager.getClassLoader(<span class="kwrd">null</span>).loadClass(fullName1).newInstance();
        System.<span class="kwrd">out</span>.println(instance);
    } <span class="kwrd">else</span> {
        System.<span class="kwrd">out</span>.println(<span class="str">"### failed"</span>);
    }

}

}

class CharSequenceJavaFileObject extends SimpleJavaFileObject {

<span class="rem">/**</span>

* CharSequence representing the source code to be compiled
*/

<span class="kwrd">private</span> CharSequence content;

<span class="rem">/**</span>

* This constructor will store the source code in the internal “content” variable and register it as a source code,
* using a URI containing the class full name

* @param className name of the public class in the source code
* @param content source code to compile
/

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

<span class="rem">/**</span>

* Answers the CharSequence to be compiled. It will give the source code stored in variable “content”
*/

@Override
<span class="kwrd">public</span> CharSequence getCharContent(boolean ignoreEncodingErrors) {
    <span class="kwrd">return</span> content;
}

}

class JavaClassObject extends SimpleJavaFileObject {

<span class="rem">/**</span>

* Byte code created by the compiler will be stored in this ByteArrayOutputStream
* so that we can later get the byte array out of it and put it in the memory as an instance of our class.
*/

<span class="kwrd">protected</span> final ByteArrayOutputStream bos = <span class="kwrd">new</span> ByteArrayOutputStream();

<span class="rem">/**</span>

* Registers the compiled class object under URI containing the class full name

* @param name Full name of the compiled class
* @param kind Kind of the data. It will be CLASS in our case
/

<span class="kwrd">public</span> JavaClassObject(String name, Kind kind) {
    super(URI.create(<span class="str">"string:///"</span> + name.replace(<span class="str">'.'</span>, <span class="str">'/'</span>) + kind.extension), kind);
}

<span class="rem">/**</span>

* Will be used by our file manager to get the byte code that can be put into memory to instantiate our class

* @return compiled byte code
/

<span class="kwrd">public</span> <span class="kwrd">byte</span>[] getBytes() {
    <span class="kwrd">return</span> bos.toByteArray();
}

<span class="rem">/**</span>

* Will provide the compiler with an output stream that leads to our byte array.
* This way the compiler will write everything into the byte array that we will instantiate later
*/

@Override
<span class="kwrd">public</span> OutputStream openOutputStream() throws IOException {
    <span class="kwrd">return</span> bos;
}

}

class ClassFileManager extends ForwardingJavaFileManager {

<span class="rem">/**</span>

* Instance of JavaClassObject that will store the compiled bytecode of our class
*/

<span class="kwrd">private</span> Map<String, JavaClassObject> map = <span class="kwrd">new</span> HashMap<String, JavaClassObject>();

<span class="rem">/**</span>

* Will initialize the manager with the specified standard java file manager
*/

<span class="kwrd">public</span> ClassFileManager(StandardJavaFileManager standardManager) {
    super(standardManager);
}

<span class="rem">/**</span>

* Will be used by us to get the class loader for our compiled class.
* It creates an anonymous class extending the SecureClassLoader which uses the byte code created by the compiler
* and stored in the JavaClassObject, and returns the Class for it
*/

@Override
<span class="kwrd">public</span> ClassLoader getClassLoader(Location location) {
    <span class="kwrd">return</span> <span class="kwrd">new</span> SecureClassLoader() {
        @Override
        <span class="kwrd">protected</span> Class<?> findClass(String name) throws ClassNotFoundException {
            JavaClassObject jclassObject = map.get(name);
            <span class="kwrd">byte</span>[] b = jclassObject.getBytes();
            <span class="kwrd">return</span> super.defineClass(name, jclassObject.getBytes(), 0, b.length);
        }
    };
}

<span class="rem">/**</span>

* Gives the compiler an instance of the JavaClassObject so that the compiler can write the byte code into it.
*/

@Override
<span class="kwrd">public</span> JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling)
        throws IOException {
    map.put(className, <span class="kwrd">new</span> JavaClassObject(className, kind));
    <span class="kwrd">return</span> map.get(className);
}

}```

comments powered by Disqus