Подтвердить что ты не робот

Преобразование строки в код

Я хочу знать, есть ли способ конвертировать String в компилируемый код Java.

У меня есть сравнительное выражение, сохраненное в поле базы данных. Я хочу получить его из базы данных, а затем оценить его в условной структуре.

Есть ли способ сделать это?

4b9b3361

Ответ 1

Если вы используете 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 в поле базы данных, ссылаясь на a и b.

Ответ 2

Используйте Groovy!

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

Ответ 3

Вопрос о том, как программно скомпилировать Java-код, представленный в виде строки , задают довольно часто и в различных формах, иногда ссылаясь на код, который хранится в базе данных или вводится пользователем.Когда я искал информацию об этом, я наткнулся на многие из этих вопросов и был разочарован, увидев, что общей рекомендацией было использование внешних инструментов (BeanShell, Groovy...).Ответ Адама Пейнтера на этот вопрос был наиболее полезным для того, чтобы как минимум выяснить соответствующие ключевые слова.Но даже обращаясь к дополнительным внешним ресурсам (например, к примеру из Java2s), я боролся за реализацию чистой компиляции в памяти одного или нескольких классов Java (которые действительно работали) с использованием только API JavaCompiler.


Итак, вот пример, показывающий весь процесс компиляции одного или нескольких классов в памяти во время выполнения, когда их исходный код задан в виде строки. Он построен на основе небольшого служебного класса 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, должно быть достаточно добавить JAR в путь к classpath вызывающего приложения. Затем JavaCompiler выберет этот путь к классам, чтобы найти классы, необходимые для компиляции.

Кажется, в этом есть какая-то магия.По крайней мере, я не разобрался в точных механизмах этого, просто проверил это на примере

И примечание: конечно, можно рассмотреть буквально произвольные расширения этого класса. Моя цель состояла в том, чтобы создать простой, автономный, легко копируемый и вставляемый пример, который показывает весь процесс и даже может быть "полезен" для определенных шаблонов приложения.

Для более сложных функций можно было бы рассмотреть возможность либо расширения этого класса соответствующим образом, либо взглянуть, например, на Java-Runtime-Compiler из проекта OpenHFT (я наткнулся на это через несколько недель после того, как написал этот ответ). Он в основном использует те же методы для внутреннего использования, но несколько более изощренно, а также предлагает специальные механизмы для обработки загрузчиков классов для внешних зависимостей.

Ответ 4

Вы не можете, потому что java - это скомпилированный язык.

Однако вы должны использовать javax.script api для выполнения кода во время выполнения. JVM6 поставляется с Rhino (javascript-интерпретатором), доступным через javax.script.

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

Есть javax.script -компонентные java-интерпретаторы (и bean shell).

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

Ответ 5

Вы можете использовать что-то вроде BeanShell.

Ответ 6

Вы можете использовать BeanShell.
Ключевым классом является bsh.Interpreter.

Вот простой пример:

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

Можно даже определить целый класс вместо одного выражения.

Ответ 7

Нельзя сказать, что это невозможно. Это очень схожая проблема с проблемой, с которой сталкиваются серверные страницы Java (JSP) - в их случае есть код, встроенный в файлы HTML, который необходимо скомпилировать в сервлет и выполнить. Если вы действительно хотели использовать этот механизм, я уверен, что вы могли бы прорвать источник для контейнера сервлетов и выяснить, как они это сделали это (возможно, даже в некоторой степени повторное использование их механизма.)

Тем не менее; это непростая задача решить (как только вы решаете очевидную, непосредственную проблему, вам приходится решать проблемы с загрузкой классов и связанными с ними проблемами.)

Конечно, было бы неплохо пойти с Java Scripting Platform в JDK6.

Ответ 8

По-видимому Java Scripting Platform лучше для этой ситуации, но вы также можете использовать Компилятор Java Api. Он предоставляет методы для компиляции исходных файлов java из java-кода. В вашем случае вы можете создать временный файл, содержащий класс с вашим сравнительным выражением, тогда вы можете загрузить этот файл и использовать его. Конечно, это не очень элегантно. Ознакомиться с http://www.juixe.com/techknow/index.php/2006/12/13/java-se-6-compiler-api/ для получения подробной информации об использовании Java Compiler Api

Ответ 10

StringEscapeUtils.escapeJava из Commons Lang может помочь, если вы хотите сгенерировать фрагмент компилируемого кода и выгрузить там строку.

Ответ 11

Простой способ получить фрагменты кода исполняемого байтового кода - это библиотека Javassist.

Вы можете адаптировать методы, описанные в http://www.ibm.com/developerworks/java/library/j-dyn0610/, чтобы соответствовать вашим потребностям.

Ответ 12

Если вы получаете условие из базы данных, я поставил бы вам хороший шанс, что вы можете использовать это условие для доступа к данным в этой базе данных.

Если вы используете ORM, например JPA или Hibernate (традиционный или JPA), вы можете сформулировать динамическое выражение запроса, которое вы передадите методу createQuery(). Это не так хорошо, как возможность выполнять "на лету" компиляцию произвольного кода Java, но, возможно, это все, что вам нужно, и это конкретное решение не требует каких-либо специальных включений или действий, поскольку компилятор языка запросов часть самой системы ORM.

Конечно, если вы делаете динамические запросы таким образом, я бы рекомендовал их каким-то образом зарегистрировать, так как это может быть реальной болью, чтобы выяснить, что пошло не так после того, как ваша строка запроса теперь находится в сборщике мусора.

Ответ 13

Вы не должны. В самом деле!

Вы изобретаете еще один механизм корпоративных правил?. Вы можете захотеть прочитать эти ссылки.

Рассмотрим тот факт, что только люди, которые достаточно квалифицированы для написания кода и затем вставляют его в базу данных, вероятно, имеют редактор и компилятор в любом случае...

Компилятор поймает все эти отвратительные синтаксические ошибки, и вы даже можете проверить код! Помните, что редакторы и компиляторы, и даже компьютерные языки были изобретены, чтобы помочь программисту удобно написать понятный код с разумными усилиями.

Пока я нахожусь: читайте о перчатки complicators тоже!

Ответ 14

Если все, что вам действительно нужно сделать, это оценить выражение, хранящееся в базе данных, вы можете посмотреть на JEP (Java Expression Parser)

Последняя (коммерческая) версия здесь.

Немного старше, версия GPL здесь

Несколько примеров использования.

Ответ 15

Если вы готовы пожертвовать частью вашего Java-кода, вы можете использовать библиотеку Java Mathematic Expression Evaluator. Он позволяет указать математическое выражение (как java.lang.String), добавить значения для переменных, а затем оценить выражение.

Я использовал его в производственном коде с большим успехом.

Ответ 16

Да, это возможно во многих отношениях.

Как уже упоминалось выше, Java 6 позволяет вам анализировать, манипулировать и переписывать код при его загрузке!

Решение может варьироваться:

Вы можете, например, написать выражение в виде Java как класс Java и вставить свой сериализованный класс в БД в виде шара или капли или любого другого его вызываемого.

Или вы можете использовать шаблон для записи класса Java в файл и вставить в него свое выражение. Затем скомпилируйте класс во время выполнения (например, JSP для сервлета), а затем динамически загрузите класс.

Конечно, вы захотите кэшировать свои скомпилированные классы для будущего использования, если они не были отредактированы в базе данных.

Тогда есть также возможность использовать встроенный механизм сценариев, такой как большинство упомянутых ответов.

Независимо от того, что вы выберете, возможно, вы сможете обновить это сообщение с информацией о вашем выборе, реализации, проблемах, заметках, комментариях и т.д.

Ответ 17

Вам гораздо лучше сопоставить информацию о состоянии базы данных с объектом и либо применить на ней метод equals, который вы можете использовать с BeanPropertyValueEqualsPredicate из сообщества apache beanutils или вместо этого реализовать Comparable. Это должно делать то, что вам нужно, без всякой магии компилятора или проблем с безопасностью преобразования строк в код.

Ответ 18

Я использовал и BeanShell, и GroovyShell, но с точки зрения производительности, GroovyShell быстрее, если вы праймируете и кешируете script.