java把字符串变代码_将字符串转换为代码

如何以编程方式编译以字符串形式提供的Java代码的问题被问相当频繁和以各种形式,有时引用存储在数据库或由用户..当我搜索有关这方面的信息时,我偶然发现了许多这样的问题,并且失望地看到,一般的建议是使用外部工具(BeanShell,Groovy.)。这个亚当·佩恩特对这个问题的回答是最有帮助的,至少找出了相关的关键词。但即使通过咨询更多的外部资源(比如Java 2的示例),我很难实现一个或多个Java类的纯内存编译(实际上是这样的)。工作过)只使用JavaCompilerAPI

下面是一个例子,展示了在运行时在内存中编译一个或多个类的整个过程,当它们的源代码以字符串的形式给出时。它是围绕一个小的实用工具类建造的,RuntimeCompiler,它只接收序列类名和相应的源代码,然后允许编译这些类并获得Class物品。

这是一个MCVE可以直接编译和执行的JDK, 不带着JRE,因为后者不包含像JavaCompiler.import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.OutputStream;

import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.lang.reflect.Modifier;

import java.net.URI;import java.util.ArrayList;import java.util.Arrays;import java.util.Collections;import java.util.Comparator;

import java.util.LinkedHashMap;import java.util.List;import java.util.Map;import javax.tools.Diagnostic;

import javax.tools.DiagnosticCollector;import javax.tools.FileObject;import javax.tools.ForwardingJavaFileManager;

import javax.tools.JavaCompiler;import javax.tools.JavaCompiler.CompilationTask;import javax.tools.JavaFileObject;

import javax.tools.JavaFileObject.Kind;import javax.tools.SimpleJavaFileObject;import javax.tools.StandardJavaFileManager;

import javax.tools.ToolProvider;/**

* An example showing how to use the RuntimeCompiler utility class

*/public class RuntimeCompilerExample{

public static void main(String[] args) throws Exception

{

simpleExample();

twoClassExample();

useLoadedClassExample();

}

/**

* Simple example: Shows how to add and compile a class, and then

* invoke a static method on the loaded class.

*/

private static void simpleExample()

{

String classNameA = "ExampleClass";

String codeA =

"public class ExampleClass {" + "\n" +

"    public static void exampleMethod(String name) {" + "\n" +

"        System.out.println(\"Hello, \"+name);" + "\n" +

"    }" + "\n" +

"}" + "\n";

RuntimeCompiler r = new RuntimeCompiler();

r.addClass(classNameA, codeA);

r.compile();

MethodInvocationUtils.invokeStaticMethod(

r.getCompiledClass(classNameA),

"exampleMethod", "exampleParameter");

}

/**

* An example showing how to add two classes (where one refers to the

* other), compile them, and invoke a static method on one of them

*/

private static void twoClassExample()

{

String classNameA = "ExampleClassA";

String codeA =

"public class ExampleClassA {" + "\n" +

"    public static void exampleMethodA(String name) {" + "\n" +

"        System.out.println(\"Hello, \"+name);" + "\n" +

"    }" + "\n" +

"}" + "\n";

String classNameB = "ExampleClassB";

String codeB =

"public class ExampleClassB {" + "\n" +

"    public static void exampleMethodB(String name) {" + "\n" +

"        System.out.println(\"Passing to other class\");" + "\n" +

"        ExampleClassA.exampleMethodA(name);" + "\n" +

"    }" + "\n" +

"}" + "\n";

RuntimeCompiler r = new RuntimeCompiler();

r.addClass(classNameA, codeA);

r.addClass(classNameB, codeB);

r.compile();

MethodInvocationUtils.invokeStaticMethod(

r.getCompiledClass(classNameB),

"exampleMethodB", "exampleParameter");

}

/**

* An example that compiles and loads a class, and then uses an

* instance of this class

*/

private static void useLoadedClassExample() throws Exception

{

String classNameA = "ExampleComparator";

String codeA =

"import java.util.Comparator;" + "\n" +

"public class ExampleComparator " + "\n" +

"    implements Comparator {" + "\n" +

"    @Override" + "\n" +

"    public int compare(Integer i0, Integer i1) {" + "\n" +

"        System.out.println(i0+\" and \"+i1);" + "\n" +

"        return Integer.compare(i0, i1);" + "\n" +

"    }" + "\n" +

"}" + "\n";

RuntimeCompiler r = new RuntimeCompiler();

r.addClass(classNameA, codeA);

r.compile();

Class> c = r.getCompiledClass("ExampleComparator");

Comparator comparator = (Comparator) c.newInstance();

System.out.println("Sorting...");

List list = new ArrayList(Arrays.asList(3,1,2));

Collections.sort(list, comparator);

System.out.println("Result: "+list);

}}/**

* Utility class for compiling classes whose source code is given as

* strings, in-memory, at runtime, using the JavaCompiler tools.

*/class RuntimeCompiler{

/**

* The Java Compiler

*/

private final JavaCompiler javaCompiler;

/**

* The mapping from fully qualified class names to the class data

*/

private final Map classData;

/**

* A class loader that will look up classes in the {@link #classData}

*/

private final MapClassLoader mapClassLoader;

/**

* The JavaFileManager that will handle the compiled classes, and

* eventually put them into the {@link #classData}

*/

private final ClassDataFileManager classDataFileManager;

/**

* The compilation units for the next compilation task

*/

private final List compilationUnits;

/**

* Creates a new RuntimeCompiler

*

* @throws NullPointerException If no JavaCompiler could be obtained.

* This is the case when the application was not started with a JDK,

* but only with a JRE. (More specifically: When the JDK tools are

* not in the classpath).

*/

public RuntimeCompiler()

{

this.javaCompiler = ToolProvider.getSystemJavaCompiler();

if (javaCompiler == null)

{

throw new NullPointerException(

"No JavaCompiler found. Make sure to run this with "

+ "a JDK, and not only with a JRE");

}

this.classData = new LinkedHashMap();

this.mapClassLoader = new MapClassLoader();

this.classDataFileManager =

new ClassDataFileManager(

javaCompiler.getStandardFileManager(null, null, null));

this.compilationUnits = new ArrayList();

}

/**

* Add a class with the given name and source code to be compiled

* with the next call to {@link #compile()}

*

* @param className The class name

* @param code The code of the class

*/

public void addClass(String className, String code)

{

String javaFileName = className + ".java";

JavaFileObject javaFileObject =

new MemoryJavaSourceFileObject(javaFileName, code);

compilationUnits.add(javaFileObject);

}

/**

* Compile all classes that have been added by calling

* {@link #addClass(String, String)}

*

* @return Whether the compilation succeeded

*/

boolean compile()

{

DiagnosticCollector diagnosticsCollector =

new DiagnosticCollector();

CompilationTask task =

javaCompiler.getTask(null, classDataFileManager,

diagnosticsCollector, null, null,

compilationUnits);

boolean success = task.call();

compilationUnits.clear();

for (Diagnostic> diagnostic : diagnosticsCollector.getDiagnostics())

{

System.out.println(

diagnostic.getKind() + " : " +

diagnostic.getMessage(null));

System.out.println(

"Line " + diagnostic.getLineNumber() +

" of " + diagnostic.getSource());

System.out.println();

}

return success;

}

/**

* Obtain a class that was previously compiled by adding it with

* {@link #addClass(String, String)} and calling {@link #compile()}.

*

* @param className The class name

* @return The class. Returns null if the compilation failed.

*/

public Class> getCompiledClass(String className)

{

return mapClassLoader.findClass(className);

}

/**

* In-memory representation of a source JavaFileObject

*/

private static final class MemoryJavaSourceFileObject extends

SimpleJavaFileObject

{

/**

* The source code of the class

*/

private final String code;

/**

* Creates a new in-memory representation of a Java file

*

* @param fileName The file name

* @param code The source code of the file

*/

private MemoryJavaSourceFileObject(String fileName, String code)

{

super(URI.create("string:///" + fileName), Kind.SOURCE);

this.code = code;

}

@Override

public CharSequence getCharContent(boolean ignoreEncodingErrors)

throws IOException

{

return code;

}

}

/**

* A class loader that will look up classes in the {@link #classData}

*/

private class MapClassLoader extends ClassLoader

{

@Override

public Class> findClass(String name)

{

byte[] b = classData.get(name);

return defineClass(name, b, 0, b.length);

}

}

/**

* In-memory representation of a class JavaFileObject

* @author User

*

*/

private class MemoryJavaClassFileObject extends SimpleJavaFileObject

{

/**

* The name of the class represented by the file object

*/

private final String className;

/**

* Create a new java file object that represents the specified class

*

* @param className THe name of the class

*/

private MemoryJavaClassFileObject(String className)

{

super(URI.create("string:///" + className + ".class"),

Kind.CLASS);

this.className = className;

}

@Override

public OutputStream openOutputStream() throws IOException

{

return new ClassDataOutputStream(className);

}

}

/**

* A JavaFileManager that manages the compiled classes by passing

* them to the {@link #classData} map via a ClassDataOutputStream

*/

private class ClassDataFileManager extends

ForwardingJavaFileManager

{

/**

* Create a new file manager that delegates to the given file manager

*

* @param standardJavaFileManager The delegate file manager

*/

private ClassDataFileManager(

StandardJavaFileManager standardJavaFileManager)

{

super(standardJavaFileManager);

}

@Override

public JavaFileObject getJavaFileForOutput(final Location location,

final String className, Kind kind, FileObject sibling)

throws IOException

{

return new MemoryJavaClassFileObject(className);

}

}

/**

* An output stream that is used by the ClassDataFileManager

* to store the compiled classes in the  {@link #classData} map

*/

private class ClassDataOutputStream extends OutputStream

{

/**

* The name of the class that the received class data represents

*/

private final String className;

/**

* The output stream that will receive the class data

*/

private final ByteArrayOutputStream baos;

/**

* Creates a new output stream that will store the class

* data for the class with the given name

*

* @param className The class name

*/

private ClassDataOutputStream(String className)

{

this.className = className;

this.baos = new ByteArrayOutputStream();

}

@Override

public void write(int b) throws IOException

{

baos.write(b);

}

@Override

public void close() throws IOException

{

classData.put(className, baos.toByteArray());

super.close();

}

}}/**

* Utility methods not directly related to the RuntimeCompiler

*/class MethodInvocationUtils{

/**

* Utility method to invoke the first static method in the given

* class that can accept the given parameters.

*

* @param c The class

* @param methodName The method name

* @param args The arguments for the method call

* @return The return value of the method call

* @throws RuntimeException If either the class or a matching method

* could not be found

*/

public static Object invokeStaticMethod(

Class> c, String methodName, Object... args)

{

Method m = findFirstMatchingStaticMethod(c, methodName, args);

if (m == null)

{

throw new RuntimeException("No matching method found");

}

try

{

return m.invoke(null, args);

}

catch (IllegalAccessException e)

{

throw new RuntimeException(e);

}

catch (IllegalArgumentException e)

{

throw new RuntimeException(e);

}

catch (InvocationTargetException e)

{

throw new RuntimeException(e);

}

catch (SecurityException e)

{

throw new RuntimeException(e);

}

}

/**

* Utility method to find the first static method in the given

* class that has the given name and can accept the given

* arguments. Returns null if no such method

* can be found.

*

* @param c The class

* @param methodName The name of the method

* @param args The arguments

* @return The first matching static method.

*/

private static Method findFirstMatchingStaticMethod(

Class> c, String methodName, Object ... args)

{

Method methods[] = c.getDeclaredMethods();

for (Method m : methods)

{

if (m.getName().equals(methodName) &&

Modifier.isStatic(m.getModifiers()))

{

Class>[] parameterTypes = m.getParameterTypes();

if (areAssignable(parameterTypes, args))

{

return m;

}

}

}

return null;

}

/**

* Returns whether the given arguments are assignable to the

* respective types

*

* @param types The types

* @param args The arguments

* @return Whether the arguments are assignable

*/

private static boolean areAssignable(Class> types[], Object ...args)

{

if (types.length != args.length)

{

return false;

}

for (int i=0; i

{

Object arg = args[i];

Class> type = types[i];

if (arg != null && !type.isAssignableFrom(arg.getClass()))

{

return false;

}

}

return true;

}}根据评论编辑:

为了编译包含在外部JAR文件中的类,只需将JAR添加到classpath调用应用程序。这个JavaCompiler然后将选择这个类路径来查找编译所需的类。

似乎涉及到了一些魔法。至少,我还没有弄清楚这背后的确切机制,只是用一个例子对其进行了测试。

还有一个旁白:当然,我们可以从字面上考虑任意这个类的扩展。我的目标是创建一个简单、独立、易于复制和传递的示例,它显示了整个过程,甚至可能对某些应用程序模式“有用”。

对于更复杂的功能,可以考虑相应地扩展这个类,或者查看一下,例如,Java-Runtime-编译器来自OpenHFT项目(我在写了这个答案几周后偶然发现了这个问题)。它基本上在内部使用相同的技术,但以一种更复杂的方式使用,并且还提供了用于处理外部依赖项的类加载器的专用机制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值