функция eval в java своими руками
Функция eval присуща скриптовым языкам, не смотря на то, что Java не является скриптовым языком и такого метода там нет, существует возможность реализовать её самим, Java предоставляет для этого инструменты. Определимся, что для этого нужно:
1) генерация кода для компиляции
2) компилирование в байт-код
3) загрузка байт-кода и его исполнение
1. Генерация кода для компиляции
Для того чтобы выполнить код переданый в функцию eval, нужно загрузить класс и вызвать метод содержащий этот код, как следствие компилируемый класс будет выглядеть следующим образом:
public void evalFunc(){
/*
* Вставляемый код для выполнения
*/
}
}
2. Компиляция кода
В Java SE 6 был добавлен пакет javax.tools, предоставляющий стандартный API для компиляции исходного кода. Компилятор не входит в jre и для компиляции необходим jdk. Для вызова Java компилятора из программы необходим доступ к интерфейсу JavaCompiler. С его помощью можно указать CLASSPATH, путь до исходников и папку где будут создаваться скомпилированные классы. Класс ToolProvider включает реализацию интерфейса JavaCompiler и содержит метод getSystemJavaCompiler(), который возвращает экземпляр JavaCompiler.
Есть 2 способа компиляции
1 способ
in, out, err — входной, выходной и ошибок потоки ввода вывода соответственно, если передан в качестве параметра null, то будет использоваться System.out и т.д. arguments это перечисление через запятую аргументов, передаваемых компилятору. В случае ошибки компиляции, возвращается не нулевой результат. Нам это вариант не совсем подходит т.к. компиляция производится из файлов и опять же в файлы.
2 способ
Это метод getTask() позволяющий более гибко подойти к вопросу компиляции
Writer out,
JavaFileManager fileManager,
DiagnosticListener diagnosticListener,
Iterable options,
Iterable classes,
Iterable compilationUnits
)
Многие из этих параметром могут быть безболезненно установлены в null, в этом случае будут использоваться значения по умолчанию
out: System.err
fileManager: стандартный файловый менеджер
diagnosticListener: стандартное поведение компилятора
options: компилятору не будет передано дополнительных опций
classes: без имён классов для обработки аннотаций
diagnosticListener — используется отловки ошибок возникших при компиляции и вывода диагностических сообщений.
Последний аргумент compilationUnits не может быть установлен в null, это как раз то, что будем компилировать.
StandardJavaFileManager содержит 2 метода, которые возвращают Iterable:
Iterable<? extends File> files)
Iterable<? extends JavaFileObject> getJavaFileObjectsFromStrings(
Iterable<String> names)
в первом случае files это объекты класса File, а во втором имена файлов. Для того же чтобы получить JavaFileObject из строки создадим класс обвёртку MemorySource:
private String src;
public MemorySource(String name, String src) {
super(URI.create("string:///" + name.replace('.','/') + Kind.SOURCE.extension),Kind.SOURCE);
this.src = src;
}
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return src;
}
}
В методе getTask устраивает всё, кроме того, что байт-код скомпилированного класса будет писаться в файл. Для того чтобы байт-код не писался в файл, создадим свой файловый менеджер:
private SpecialClassLoader classLoader;
public SpecialJavaFileManager(StandardJavaFileManager fileManager, SpecialClassLoader specClassLoader) {
super(fileManager);
classLoader = specClassLoader;
}
public JavaFileObject getJavaFileForOutput(Location location, String name, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
MemoryByteCode byteCode = new MemoryByteCode(name);
classLoader.addClass(byteCode);
return byteCode;
}
}
SpecialClassLoader — загрузчик классов который будет рассмотрен в 3 части.
Единственный метод, который нужно будет переопределить для нашей цели это getJavaFileForOutput, который должен возвращать объект класса JavaFileObject. В этом возвращаемом объекте и кроется вся магия записи байт-кода в память, этот объект класса MemoryByteCode:
private ByteArrayOutputStream oStream;
public MemoryByteCode(String name) {
super(URI.create("byte:///" + name.replace('/','.') + Kind.CLASS.extension),Kind.CLASS);
}
public OutputStream openOutputStream() {
oStream = new ByteArrayOutputStream();
return oStream;
}
public byte[] getBytes() {
return oStream.toByteArray();
}
}
openOutputStream переопределённый метод из SimpleJavaFileObject, с помощью которого и будет байт-код записываться в память, а не в файл.
getBytes нужен, чтобы потом получить этот самый байт-код.
3. Загрузка скомпилированного кода.
про загрузку java-классов хорошо написано в тут Блог сурового челябинского программиста: Пишем свой загрузчик java-классов, поэтому, сразу рассмотрю код загрузчика
private MemoryByteCode byteCode;
protected Class<?> findClass(String name) {
return defineClass(name, byteCode.getBytes(), 0, byteCode.getBytes().length);
}
public void addClass(MemoryByteCode code) {
byteCode = code;
}
}
загрузчик прост, метод addClass запоминает скомпилированный байт-код.
И при необходимости создаётся экземпляр этого класса.
Результат:
Собрав всё воедино получаем класс Eval:
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.URI;
import java.util.Arrays;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import javax.tools.JavaCompiler.CompilationTask;
public class Eval {
public void exec(String code) throws Exception {
String src = "public class SpecialClassToCompile {"+
"public void evalFunc(){"+
code+
"}"+
"}";
SpecialClassLoader classLoader = new SpecialClassLoader();
compileMemoryMemory(src, "SpecialClassToCompile", classLoader, System.err);
Class<?> c = Class.forName("SpecialClassToCompile", false, classLoader);
Object o = c.newInstance();
c.getMethod("evalFunc", new Class[] {}).invoke(o, new Object[] { });
}
public void compileMemoryMemory(String src, String name, SpecialClassLoader classLoader, PrintStream err) {
JavaCompiler javac = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector<JavaFileObject> diacol = new DiagnosticCollector<JavaFileObject>();
StandardJavaFileManager standartFileManager = javac.getStandardFileManager(diacol, null, null);
SpecialJavaFileManager fileManager = new SpecialJavaFileManager(standartFileManager, classLoader);
CompilationTask compile = javac.getTask(null, fileManager, diacol, null, null,
Arrays.asList(new JavaFileObject[] { new MemorySource(name, src) }));
boolean status = compile.call();
if(err != null) {
err.println("Compile status: " + status);
for(Diagnostic<? extends JavaFileObject> dia : diacol.getDiagnostics()) {
err.println(dia);
}
}
}
}
/**
* Класс для создания кода из строки
* @author vampirus
*
*/
class MemorySource extends SimpleJavaFileObject {
private String src;
public MemorySource(String name, String src) {
super(URI.create("string:///" + name.replace('.','/') + Kind.SOURCE.extension),Kind.SOURCE);
this.src = src;
}
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return src;
}
}
/**
* Класс для записи байткода в память
* @author vampirus
*
*/
class MemoryByteCode extends SimpleJavaFileObject {
private ByteArrayOutputStream oStream;
public MemoryByteCode(String name) {
super(URI.create("byte:///" + name.replace('/','.') + Kind.CLASS.extension),Kind.CLASS);
}
public OutputStream openOutputStream() {
oStream = new ByteArrayOutputStream();
return oStream;
}
public byte[] getBytes() {
return oStream.toByteArray();
}
}
/**
* Файловый менеджер
* @author vampirus
*
*/
class SpecialJavaFileManager extends ForwardingJavaFileManager<StandardJavaFileManager> {
private SpecialClassLoader classLoader;
public SpecialJavaFileManager(StandardJavaFileManager fileManager, SpecialClassLoader specClassLoader) {
super(fileManager);
classLoader = specClassLoader;
}
public JavaFileObject getJavaFileForOutput(Location location, String name, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
MemoryByteCode byteCode = new MemoryByteCode(name);
classLoader.addClass(byteCode);
return byteCode;
}
}
/**
* Загрузчик
* @author vampirus
*
*/
class SpecialClassLoader extends ClassLoader {
private MemoryByteCode byteCode;
protected Class<?> findClass(String name) {
return defineClass(name, byteCode.getBytes(), 0, byteCode.getBytes().length);
}
public void addClass(MemoryByteCode code) {
byteCode = code;
}
}
тестим:
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Eval e = new Eval();
try {
e.exec("System.out.println("testing");");
e.exec("System.out.println("testing2");");
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
Естественно, применение такого способа не ограничивается функцией eval, полезность которой, вообще говоря, весьма сомнительна.
28.01.2009 в 21:42
А чем нельзя использовать либу javaassist, которой jdk не нужен?
Стоит еще отметить, что кодогенерацию, например, запрещено использовать по стандарту J2ee 5. Т.е. под сервером приложений код работать не будет.
29.01.2009 в 06:42
А чем нельзя использовать либу javaassist, которой jdk не нужен?
Стоит еще отметить, что кодогенерацию, например, запрещено использовать по стандарту J2ee 5. Т.е. под сервером приложений код работать не будет.
29.01.2009 в 02:22
Ну да это, не для серверной платформы.
29.01.2009 в 11:22
Ну да это, не для серверной платформы.
12.12.2009 в 13:09
Неплохо. Вопрос на засыпку автору — для решения каких реальных практических задач пригодится run time компиляция?
12.12.2009 в 13:18
Нам она была в нужна какой-то сомнительной вещи для генерации/тестирования автоматов в рантайме в среде unimod :), но тогда этого сделать не удалось, немного спустя, когда и надобность в этом пропала решил попробовать всё-таки сделать её.
12.12.2009 в 19:09
Неплохо. Вопрос на засыпку автору — для решения каких реальных практических задач пригодится run time компиляция?
12.12.2009 в 19:18
Нам она была в нужна какой-то сомнительной вещи для генерации/тестирования автоматов в рантайме в среде unimod :), но тогда этого сделать не удалось, немного спустя, когда и надобность в этом пропала решил попробовать всё-таки сделать её.
12.01.2012 в 00:48
А вы не могли бы ссылку кинуть на спецификацию, где запрещается использовать javax.tool в J2EE?
22.01.2012 в 05:44
Просто бомба, то что надо, искал такое пол дня, конечно переделал под нужные задачи, но ОЧЕНЬ СПАСИБО, конечно было бы лучше бы без jdk, через jre, но хоть что-то))