将字符串转换为代码

时间:2009-06-01 14:54:49

标签: java

我想知道是否有任何方法可以将String转换为Java可编译代码。

我将比较表达式保存在数据库字段中。我想从数据库中检索它,然后在条件结构中对它进行评估。

有没有办法做到这一点?

18 个答案:

答案 0 :(得分:40)

如果您使用的是Java 6,则可以尝试使用Java Compiler 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表达式存储在数据库字段中,引用ab

答案 1 :(得分:22)

使用Groovy!

Binding binding = new Binding();
GroovyShell shell = new GroovyShell(binding);
Object value = shell.evaluate("for (x=0; x<5; x++){println "Hello"}; return x");

答案 2 :(得分:19)

The question of how to programmatically compile Java Code that is given as a String is asked quite frequently and in various forms, sometimes referring to code that is stored in a database or entered by the user. When I searched for information about this, I stumbled upon many of these questions, and was disappointed to see that the general recommendation was to use external tools (BeanShell, Groovy...). The answer by Adam Paynter to this question was the most helpful in order to at least figure out the relevant keywords. But even by consulting further external resources (like an example from Java2s), I struggled with implementing a pure in-memory compilation of one or more Java classes (that actually worked) using only the JavaCompiler API.


So here is an example showing the whole process of compiling one or multiple classes in-memory, at runtime, when their source code is given as a string. It is built around a small utility class, RuntimeCompiler, that simply receives a sequence class names and the corresponding source codes, and then allows compiling these classes and obtaining the Class objects.

It is a MCVE that can be compiled and executed directly - with a JDK, not with a JRE, because the latter does not contain the tools like the 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;
    }

}

EDIT In response to the comment:

In order to compile classes that are contained in external JAR files, it should be sufficient to add the JAR to the classpath of the calling application. The JavaCompiler will then pick up this classpath to find classes that it needs for the compilation.

There seems to be some magic involved. At least, I have not figured out the exact mechanisms behind that, just tested it with an example

And a side note: Of course, one could consider literally arbitrary extensions to this class. My goal was to create a simple, standalone, easily copy-and-pastable example that shows the whole process, and may even be "useful" for certain application patterns.

For more sophisticated functionalities, one could consider either extending this class accordingly, or have a look, for example, at the Java-Runtime-Compiler from the OpenHFT project (I stumbled upon this a few weeks after I had written this answer). It basically uses the same techniques internally, but in a somewhat more sophisticated way, and also offers dedicated mechanisms for handling class loaders for external dependencies.

答案 3 :(得分:8)

您可以使用类似BeanShell的内容。

答案 4 :(得分:8)

您可以使用BeanShell 关键类是bsh.Interpreter

这是一个简单的例子:

Interpreter interpreter = new Interpreter();
Object res = interpreter.eval("your expresion");

甚至可以定义整个类而不是单个表达式。

答案 5 :(得分:8)

你不能因为java是一种编译语言。

但是,您应该使用javax.script api在运行时执行代码。 JVM6通过javax.script附带Rhino(javascript解释器)。

http://java.sun.com/javase/6/docs/api/javax/script/package-summary.html

javax.script兼容的java解释器(和bean shell)可用。

https://scripting.dev.java.net/

答案 6 :(得分:3)

Groovy也可能是你的选择。

它与Bean Scripting Framework完全集成,可以embedded directly非常容易,并且对于您来说,语法方面可能没问题。

答案 7 :(得分:3)

显然Java Scripting Platform对于这种情况更好,但您也可以使用Java Compiler Api。它提供了从java代码中编译java源文件的方法。在您的情况下,您可以创建一个包含具有比较表达式的类的临时文件,然后您可以加载该文件并使用它。当然这不是很优雅。有关使用Java Compiler Api

的详细信息,请查看http://www.juixe.com/techknow/index.php/2006/12/13/java-se-6-compiler-api/

答案 8 :(得分:3)

说这是不可能的,这是不公平的。这与Java Server Pages(JSP)所具有的问题非常相似 - 在他们的例子中,HTML文件中嵌入了需要编译成servlet并执行的代码。如果你真的想使用这种机制,我相对肯定你可以挖掘source for a servlet container并弄清楚他们是如何做到的(甚至可能在一定程度上重用它们的机制。)

然而;这不是一个容易解决的问题(一旦你解决了明显的,直接的问题,你最终不得不处理类加载和相关问题的问题。)

在JDK6中使用Java Scripting Platform似乎更好。

答案 9 :(得分:3)

如果你想生成一段可编译的代码并在那里转储一个字符串,来自Commons Lang的

StringEscapeUtils.escapeJava可能会有所帮助。

答案 10 :(得分:1)

将代码片段转换为可执行字节代码的简单方法是使用Javassist库。

您可以调整http://www.ibm.com/developerworks/java/library/j-dyn0610/中描述的技术以满足您的需求。

答案 11 :(得分:1)

如果您从数据库中获取条件,我会下注您很可能想要使用该条件访问该数据库中的数据。

如果您正在使用诸如JPA或Hibernate(传统或JPA)之类的ORM,您可能能够制定一个动态查询表达式,并将其传递给createQuery()方法。这不像能够对任意Java代码进行动态编译那么好,但也许这就是你所需要的,而且特定的解决方案不需要任何特殊的包含或动作,因为查询语言编译器是ORM系统本身的一部分。

当然,如果您以这种方式进行动态查询,我建议以某种方式记录它们,因为如果您的查询字符串现在位于垃圾收集器中,那么找出事后出现的问题可能会非常痛苦。

答案 12 :(得分:1)

你不应该。真!

你是在发明另一个enterprise rules engine?吗?您可能需要read这些links.

考虑这样一个事实:只有那些熟练编写代码然后将其插入数据库的人才可能拥有编辑器和编译器......

编译器将捕获所有那些讨厌的语法错误,你甚至可以测试代码!请记住,编辑器和编译器,甚至是计算机语言都是为了帮助程序员以合理的努力轻松编写可理解的代码而发明的。

我在谈论它时:请阅读complicators gloves

答案 13 :(得分:1)

如果您真正需要做的是评估存储在数据库中的表达式,您可能需要查看JEP(Java Expression Parser)

最新(商业)版本为here

稍微过时的GPL版本为here

Some examples for usage

答案 14 :(得分:1)

如果您愿意牺牲要求的“Java代码”部分,可以使用Java Mathematic Expression Evaluator库。它允许您指定数学表达式(作为java.lang.String),为变量添加值,然后计算表达式。

我在生产代码中使用它非常成功。

答案 15 :(得分:1)

是的,它可以在很多方面实现。

正如上面提到的,Java 6允许您在加载代码时解析,操作和重写代码!

解决方案可能有所不同:

例如,您可以将数据库表达式编写为Java类,并将序列化类作为glob或blob或其调用的任何内容插入到数据库中。

或者,您可以使用模板将Java类写入文件并将表达式插入其中。然后在运行时编译类(比如JSP到Servlet),然后动态加载类。

当然,如果尚未在数据库中编辑它们,您需要缓存已编译的类以供将来使用。

然后还可以选择使用集成的脚本引擎,例如大多数提到的响应。

无论您选择什么,也许您都可以使用您的选择,实施,问题,备注,评论等详细信息更新此帖子。

答案 16 :(得分:1)

最好将数据库条件信息映射到一个对象,并在其上实现一个equals方法,您可以使用来自apache commons beanutils的BeanPropertyValueEqualsPredicate或实现Comparable。 如果没有将字符串转换为代码的所有神奇的编译器肮脏或安全问题,这应该做你所追求的。

答案 17 :(得分:0)

我已经使用了BeanShell和GroovyShell但性能明智,如果你删除和缓存脚本,GroovyShell会更快。