将字符串转换为代码
我想知道是否有任何方法将String
转换为Java可编译的代码。
我有一个比较表达式保存在数据库字段。 我想从数据库中检索它,然后在条件结构中进行评估。
有没有办法做到这一点?
如果您正在使用Java 6,则可以尝试Java编译器API。 其核心是JavaCompiler类。 您应该能够在内存中构建Comparator
对象的源代码。
警告:我没有真正尝试下面的代码,因为JavaCompiler对象在我的平台上不可用,出于某种奇怪的原因…
警告:编译任意的Java代码可能会危害您的健康。
考虑自己警告…
String comparableClassName = ...; // the class name of the objects you wish to compare String comparatorClassName = ...; // something random to avoid class name conflicts String source = "public class " + comparatorClassName + " implements Comparable<" + comparableClassName + "> {" + " public int compare(" + comparableClassName + " a, " + comparableClassName + " b) {" + " return " + expression + ";" + " }" + "}"; JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); /* * Please refer to the JavaCompiler JavaDoc page for examples of the following objects (most of which can remain null) */ Writer out = null; JavaFileManager fileManager = null; DiagnosticListener<? super JavaFileObject> diagnosticListener = null; Iterable<String> options = null; Iterable<String> classes = null; Iterable<? extends JavaFileObject> compilationUnits = new ArrayList<? extends JavaFileObject>(); compilationUnits.add( new SimpleJavaFileObject() { // See the JavaDoc page for more details on loading the source String } ); compiler.getTask(out, fileManager, diagnosticListener, options, classes, compilationUnits).call(); Comparator comparator = (Comparator) Class.forName(comparableClassName).newInstance();
在此之后,您只需在数据库字段中存储相应的Java表达式,即引用a
和b
。
使用Groovy!
Binding binding = new Binding(); GroovyShell shell = new GroovyShell(binding); Object value = shell.evaluate("for (x=0; x<5; x++){println "Hello"}; return x");
如何以编程方式编译以String 形式给出的Java代码的问题经常 以各种形式提出 ,有时涉及存储在数据库中或由用户输入的代码。 当我搜索有关这方面的信息时,我偶然发现了许多这些问题,并且很失望地看到一般的建议是使用外部工具(BeanShell,Groovy …)。 亚当·佩恩特(Adam Paynter)对这个问题的回答是最有用的,至少可以找出相关的关键字。 但即使通过咨询更多的外部资源( 比如来自Java2的例子 ),我仍然努力实现仅使用JavaCompiler
API的一个或多个Java类(实际上工作 )的纯内存编译。
下面是一个例子,展示了在运行时编译一个或多个内存类的过程,当它们的源代码是以字符串的形式给出的。 它建立在一个小的实用程序类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<Integer> {" + "\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<Integer> comparator = (Comparator<Integer>) c.newInstance(); System.out.println("Sorting..."); List<Integer> list = new ArrayList<Integer>(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<String, byte[]> 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<JavaFileObject> 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<String, byte[]>(); this.mapClassLoader = new MapClassLoader(); this.classDataFileManager = new ClassDataFileManager( javaCompiler.getStandardFileManager(null, null, null)); this.compilationUnits = new ArrayList<JavaFileObject>(); } /** * 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<JavaFileObject> diagnosticsCollector = new DiagnosticCollector<JavaFileObject>(); 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 <code>null</code> 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<StandardJavaFileManager> { /** * 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 <code>null</code> 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<types.length; i++) { Object arg = args[i]; Class<?> type = types[i]; if (arg != null && !type.isAssignableFrom(arg.getClass())) { return false; } } return true; } }
编辑在回应评论:
为了编译包含在外部JAR文件中的classpath
,只需将JAR添加到调用应用程序的classpath
就足够了。 然后, JavaCompiler
将选取此类路径以查找编译所需的类。
似乎有一些魔术参与。 至少,我还没有弄清楚背后的确切机制,只是用一个例子来进行测试
还有一个方面:当然,可以考虑从字面上任意扩展到这个类。 我的目标是创建一个简单的,独立的,易于复制和粘贴的示例,显示整个过程,甚至可能对某些应用程序模式“有用”。
对于更复杂的功能,可以考虑扩展这个类,或者看看OpenHFT项目的Java-Runtime-Compiler (我在写这个答案几周后就偶然发现了这个)。 它基本上在内部使用相同的技术,但是以一种更复杂的方式,还提供了用于处理外部依赖的类加载器的专用机制。
你不能因为java是一种编译语言。
但是,您应该使用javax.script
api在运行时执行代码。 JVM6附带可通过javax.script
提供的Rhino(JavaScript解释器)。
http://java.sun.com/javase/6/docs/api/javax/script/package-summary.html
有javax.script
-compatible java解释器(和bean外壳)可用。
你可以通过使用像BeanShell的东西。
使用BeanShell: 链接文本键类是bsh.Interpreter。 简单的例子:
Interpreter interpreter = new Interpreter(); Object res = interpreter.eval("your expresion");
甚至可以定义整个类而不是表达式。
说这是不可能的,这是不公平的。 这与Java服务器页面(JSP)有一个非常类似的问题 – 在他们的情况下,HTML代码中嵌入了需要编译成servlet并执行的代码。 如果你真的想使用这种机制,我相对确定你可以挖掘一个servlet容器的源代码,并弄清楚它们是如何做到的(甚至可能在某种程度上重用了它们的机制)。
然而; 这不是一个容易解决的问题(一旦你解决了显而易见的问题,你最终不得不处理类加载和相关问题的问题。)
在JDK6中使用Java脚本平台似乎更好。
显然Java脚本平台更适合这种情况,但您也可以使用Java编译器Api 。 它提供了从java代码中编译java源文件的方法。 在你的情况下,你可以用一个比较表达式来创建一个包含类的临时文件,然后你可以加载该文件并使用它。 当然这不是很优雅。 查阅http://www.juixe.com/techknow/index.php/2006/12/13/java-se-6-compiler-api/了解使用Java编译器Api
Groovy也可能是你的选择。
它与Bean脚本框架整合在一起,可以很容易地直接嵌入 ,对于你来说可能是正确的,语法上的。
Commons Lang的StringEscapeUtils.escapeJava可以帮助您生成一段可编译代码并在那里转储一个字符串。
获取代码片段到可执行字节代码的简单方法是使用Javassist库。
您可以调整http://www.ibm.com/developerworks/java/library/j-dyn0610/中描述的技术以适应您的需求。;
如果你从数据库中获得条件,我敢打赌你很可能想用这个条件来访问数据库中的数据。
如果您正在使用诸如JPA或Hibernate(传统或JPA)之类的ORM,则可以构建一个动态查询表达式,并将其传递给createQuery()方法。 这并不能完成对任意Java代码的即时编译,但也许是您所需要的,而且这个特定的解决方案不需要任何特殊的包含或操作,因为查询语言编译器是ORM系统本身的一部分。
当然,如果你这样做的动态查询,我建议以某种方式记录他们,因为如果你的查询字符串现在在垃圾回收器中,那么确定后发生了什么是一个真正的痛苦。
你不应该。 真!
你在发明另一个企业规则引擎吗? 。 您可能需要阅读这些链接。
考虑一下这样一个事实:只有熟练的编写代码然后将其插入到数据库中的人才可能拥有编辑器和编译器。
编译器将捕获所有这些麻烦的语法错误,你甚至可以测试代码! 请记住,编辑和编译器,甚至是计算机语言都是为了帮助程序员用合理的努力编写易于理解的代码而发明的。
当我在这里:阅读有关复杂手套的事情!
如果您真正需要做的是评估存储在数据库中的表达式,则可能需要查看JEP(Java表达式解析器)
最新的(商业)版本在这里 。
稍微老一点的GPL版本就在这里
使用的一些例子 。
如果您愿意牺牲您的需求的“Java代码”部分,则可以使用Java Mathematic Expression Evaluator库。 它允许你指定一个数学表达式(作为java.lang.String),为变量添加值,然后计算表达式。
我已经用它在生产代码中取得了巨大的成功。
是的,这在许多方面是可能的。
就像上面提到的那样,Java 6允许你在加载的时候解析,处理和重写代码!
解决方案可以变化:
例如,你可以将你的数据库表达式写成一个Java类,并将你的序列化的类作为一个glob或者blob或者任何它所调用的东西插入到数据库中。
或者,您可以使用模板将Java类写入文件并将表达式插入到文件中。 然后在运行时编译类(如JSP到Servlet),然后动态加载类。
当然,如果数据库中没有编辑过,那么你就需要缓存编译后的类以备将来使用。
那么也有使用集成脚本引擎的选项,例如大多数提到的响应。
无论你选择什么,也许你可以更新这个职位,包括你的选择,实施,问题,笔记,评论等细节。
将数据库条件信息映射到一个对象,并实现一个equals方法,你可以使用BeanPropertyValueEqualsPredicate从apache的commons beanutils或者实现Comparable。 这应该做你以后没有所有的魔术编译器的坏处或将字符串转换为代码的安全问题。
我已经同时使用BeanShell和GroovyShell,但性能明智,如果您删除和缓存脚本GroovyShell速度更快。