В Java поддерживается возможность выполнения программного кода на лету. То есть, можно выполнить код, изначально переданный в виде строки непосредственно во время работы программы.
Так как непосредственная реализация подобного функционала сопряжена с определёнными сложностями, лучшим решением будет применение готовых инструментов. Наиболее известным из них является библиотека BeanShell (ссылка на официальный сайт в конце статьи).
Ядро библиотеки – класс Interpreter и его метод eval, который принимает в качестве параметра строку с программным кодом на языке Java и выполняет его.
Основы выполнения кода на лету
Для того чтобы выполнить код необходимо:
- Создать строку с текстом кода;
- Создать объект класса Interpreter;
- Передать строку с кодом в метод eval.
Ниже приведён простейший пример выполнения кода во время работы программы.
1 2 3 4 5 6 7 |
String code="System.out.println(\"Hello, World!\")"; Interpreter i = new Interpreter(); try { i.eval(code); } catch (EvalError ex) { System.out.println(ex.getMessage()); } |
В результате на консоль будет выведено сообщение «Hello, World!»
Важной особенностью библиотеки BeanShell является способность адекватно отслеживать ошибки в выполняемом коде. Если код, переданный в метод eval, содержит ошибки, возбуждается исключение типа EvalError. В сообщении этого исключения содержится подробная информация о возникшей проблеме.
Например. Если первую строку вышеприведённого примера исказить вот так:
1 |
String code="System.out1.println(\"Hello, World!\")"; |
В консоли будет выведено сообщение об ошибке:
Sourced file: inline evaluation of:
System.out1.println(«Hello World!»);» : No static field or inner class: out1 of class java.lang.System
Выполнение на лету сложных конструкций
Возможности библиотеки BeanShell не исчерпываются только выполнением отдельных операторов. В строке кода может содержаться и гораздо более сложная логика, даже включающая классы.
При этом сам алгоритм выполнения кода остаётся неизменным. Если переписать строку кода примера следующим образом:
1 |
String code="class TestClass{public void run(){System.out.println(\"Run method of class!\");}}TestClass test = new TestClass();test.run();"; |
Будет вначале создан объект класса TestClass, затем вызван его метод run и на консоль будет выведено сообщение «Run method of class!».
Обмен данными с основной программой
Рассмотрим следующий пример. Модифицируем предыдущий пример кода так чтобы метод run класса TestClass возвращал целочисленное значение и присвоим это значение некоторой переменной.
1 |
String code="class TestClass{public int run(int a){return a+1;}}TestClass test = new TestClass();int b=test.run(5);"; |
Для того чтобы получить значение из переменной b необходимо воспользоваться методом get класса Interpreter, который возвращает значение переменной по её имени в формате Object.
1 2 |
i.eval(code); int с = (int) i.get("b"); |
В результате переменная c будет иметь значение 6.
Передача значений в переменные, объявленные в выполняемом коде, производится с помощью метода set. Однако процесс передачи переменных имеет ряд особенностей.
Дело в том, что при передаче значения обратиться к переменной напрямую, как это было при получении, нельзя. Такая возможность не поддерживается. Вместо этого значение передаётся в именованный параметр и уже оттуда в саму переменную. Именно в такой параметр и передаёт значение метод set.
Объявляем параметр в строке с кодом. Для примера назовём его просто param.
1 |
String code = "class TestClass{public int run(int a){return a+1;}}TestClass test = new TestClass(); int p=param; int b=test.run(p);"; |
Данный параметр передаётся в переменную p, которая, в свою очередь, передаётся в метод run класса TestClass.
Передадим в параметр некоторое значение. Например, число 6.
1 |
i.set("param", 6); |
Выполним код из строки и получим значение переменной содержащей результат, как в предыдущем примере.
1 2 |
i.eval(code); int c = (int) i.get("b"); |
В итоге значение переменной c будет равно 7.
Резюме
Библиотека BeanShell не блещет разнообразием всевозможных классов (чего нельзя сказать о пространстве имён System.CodeDom.Compiler в C#). Но, несмотря на это она предоставляет разработчику очень мощный инструментарий для расширения возможностей по созданию приложений. Причём он выполнен предельно компактно и лаконично. Ничего лишнего. Только нужный функционал.
Использование данной библиотеки позволяет значительно упростить написание на Java многих технически сложных приложений. В частности, приложений, которые поддерживают динамическое расширение своего функционала, в том числе посредством собственных скриптовых языков.
Однако даже если речь идёт о более простых задачах, она станет ценным помощником везде, где необходимо выполнять код непосредственно во время работы программы.
Добавить комментарий