- PVSM.RU - https://www.pvsm.ru -
Хотелось бы начать с предыстории. В данный момент я разрабатываю некое веб-приложение на Java, ничего необычного, но в документе от заказчика есть требование: будущие администраторы приложения должны иметь возможность налету подгружать код бизнес логики на сервер. Вроде бы ничего сверхъестественного, нужно будет сделать подгрузку java-классов, думал я, пока на днях мне в голову не пришла идея: “А что, если дать возможность программировать методы бизнес логики на JavaScript?”.
В тот момент идея показалась мне очень хорошей, и я видел целый ряд преимуществ этой идеи перед простой подгрузкой java-классов:
Но не стоит забывать, что большая сила — это большая ответственность, поэтому, вслед за новыми возможностями, появляются новые вопросы, которые требуют развернутого ответа.
В этой статье я бы хотел вкраце рассказать об идее описания бизнес логики на чистом js, затронуть теоретическую и практическую части, а так же описать некоторые нюансы, которые могут возникнуть вслед за этим решением.
За движком, который будет интерпретировать для js далеко ходить не пришлось, первым же кандидатом является Mozilla Rhino — js движок, написанный полностью на Java.
Движок берет свое начало от 1997 года, родившись в стенах Netscape под именем “Javagator”, но позже, при загадочных обстоятельствах, был переименован в Rhino. “Носорог” лицензировался некоторыми крупными компаниями(включая Sun) для своих проектов. Изначально идея была в том, что бы компилировать Java-байт код на основе JavaScript, но этот подход имел некоторые проблемы:
В 1998 году в движок добавлен по умолчанию режим интерпретатора, т.е. JavaScript код исполняется на лету, минуя компиляцию в байт код. Это позволило избежать проблем с тяжелой компиляцией и утечками памяти. Позже в этом году движок открывает исходный код и отдается mozilla.org.
Скачать бинарники и исходный код библиотеки можно на оф.сайте: ссылка на сайт [1]. В примерах будет использоваться rhino1.7 R4.
Rhino можно использовать двумя способами: из командной строки и напрямую, встраивая jar-файл в проект. Нас сейчас интересует именно второй способ — добавляем в проект файл js.jar.
Одно из основных идей Rhino — возможность программировать Java-сервер при помощи JavaScript-кода, используя технологию LiveConnect. Технология дает возможность обращаться к Java-классам напрямую из js, не прибегая к помощи какого-то стороннего кода.
Вот небольшой пример обращения к классам File
и System
:
var f = new java.io.File(“test.txt”);
java.lang.System.out.println(f.exists());
LiveConnect дает нам большое пространство для действий, о нем много написано в оф. документации движка, но я не буду сейчас останавливаться на нем, т.к. нас интересует интерпретирование кода из не доверенного внешнего источника, который не должен иметь доступа к Java-классам.
В качестве примера разработаем простенький модуль бизнес логики для гипотетического проекта “Сервис учета учеников и их оценок” для университета. Приложение должно хранить список всех учеников и их оценки, а так же уметь производить над ними какие-то действия, исходя из поставленных задач.
Начнем, пожалуй, с основ использования. Rhino имеет 2 основных понятия — это контекст(класс org.mozilla.javascript.Context
) и сфера(класс org.mozilla.javascript.Scriptable
). Контекст — это инстанс интерпретатора, который привязывается к одному потоку, следовательно, интерпретирует js в едином потоке. Сфера — это так называемый namespace, в котором мы определяем все интересующие нас переменные.
Пример создания контекста и сферы:
// Создаем контекст
Context context = Context.enter();
// Создаем сферу
Scriptable scope = context.initStandardObjects();
После того, как мы создали контекст и сферу, мы должны ограничить интерпретатору доступ к Java-классам. Это делается при помощи метода setClassShutter
экземпляра контекста:
// Ограничиваем доступ LiveConnect к Java-классам
context.setClassShutter(new ClassShutter() {
@Override
public boolean visibleToScripts(String fullClassName) {
// Определяет, виден ли класс с именем fullClassName скрипту
return false;
}
});
По умолчанию Rhino использует технологию LiveConnect, которая дает доступ к java-классам прямо из js. Она дает большие возможности доверенному коду, но у нас иной случай — наш сервер будет интерпретировать потенциально небезопасный код.
Будет весьма неприятно, если в интерпретатор попадет js-код такого вида:
java.lang.System.exit(0);
Поэтому, мы попросту “затыкаем” LiveConnect и оставляем доступ лишь к тем классам, которые нам нужны. После того, как мы получили контекст и сферу, нам не остается ничего другого, кроме как интерпретировать js-код:
String script = “var mathStuff = Math.cos(Math.PI)”;
c.evaluateString(scope, script, null, 1, null);
Вот и все, после работы с Rhino, завершаем работу с контекстом и освобождаем ресурсы:
Context.exit();
Теперь, когда мы знаем, как начать работать с Rhino, можно переходить к определению внешнего API бизнес логики в виде нескольких константных ссылок на модули верхнего уровня:
public class DatabaseModule {
public DatabaseModule(){
}
/* Метод возвращает ФИО студента, принимая его идентификатор в качестве аргумента */
public String getStudent(int id){
return (id > 0) ? "Шевченко Константин Викторович" : null;
}
/* Метод возвращает оценку студента, принимая его ФИО в качестве аргумента */
public int getRating(String student){
return student.equals("Шевченко Константин Викторович") ? 5 : -1;
}
/* Метод указывает новую оценку студента, принимая его ФИО и новую оценку в качестве аргументов */
public void setRating(String student, int newRating){
System.out.println("setRating() student = "+student+", newRating = "+newRating);
// Do something
}
}
public class NotificationModule {
public NotificationModule(){
}
/* Метод оповещает студента сообщением */
public void notifyStudent(String student, String message){
System.out.println("notifyStudent() student = "+student+", message = "+message);
}
/* Метод оповещает куратора */
public void notifyCurator(String message){
System.out.println("notifyCurator() message = "+message);
}
}
Далее определяем константные ссылки на модули в ранее определенной сфере:
// Маппим DatabaseModule в js
DatabaseModule database = new DatabaseModule();
Object wrappedDatabaseModule = Context.javaToJS(database, scope);
ScriptableObject.putConstProperty(scope, "databaseModule", wrappedDatabaseModule);
// Маппим NotificationModule в js
NotificationModule notification = new NotificationModule();
Object wrappedNotificationModule = Context.javaToJS(notification, scope);
ScriptableObject.putConstProperty(scope, "notificationModule", wrappedNotificationModule);
Предположим, что перед нами встала задача: выбрать из базы данных информацию о студенте по его идентификатору, получить его оценку, посчитать, достаточно ли он имеет баллов, что бы получить допуск на экзамен и оповестить куратора и самого студента. В этом случае все задание ляжет на вот такой вот js-код:
var student = databaseModule.getStudent(1);
var rating = databaseModule.getRating(student);
var pass = rating >= 40;
if(pass){
notificationModule.notifyCurator("Student "+student+" is admitted to the exam.");
notificationModule.notifyStudent(student, "You admitted to the exam.");
} else {
var dif = 40 - rating;
notificationModule.notifyCurator("Student "+student+" needs "+dif+" points to be admitted to the exam.");
notificationModule.notifyStudent(student, "You need "+dif+" points to be admitted to the exam.");
}
Все, что остается, это настроить интерпретатор и передать ему js. Предупрежу сразу: движок болезненно воспринимает кириллицу.
В заключении скажу, что идея о возможности программирования бизнес логики на js довольно интересна, хотя и не нова. Такой подход дает некоторую гибкость и удобство внедрения.
Программист, перед которым стоит задача добавить новый метод, может не задумываться о том, какой стек технологий используется в серверной части, а просто прописывает то, что требуется сделать, с легкостью расширять и дополнять функционал.
Следом за возможностями, которые несет данный подход, идет ряд вопросов, о которых необходимо позаботиться, прежде чем внедрять его в боевой сервер:
К счастью, все вопросы вполне решаемы, исходный код Rhino отркрыт для модификации.
Оф. сайт проекта: https://developer.mozilla.org/ru/docs/Rhino [2]
Статья на Википедии: http://ru.wikipedia.org/wiki/Rhino [3]
Оф. документация: https://developer.mozilla.org/en-US/docs/Rhino_documentation [4]
API Reference (не оф.): http://tool.oschina.net/uploads/apidocs/rhino/ [5]
Исходники примера на GitHub: https://github.com/andrew-medvedev/rhino-example [6]
Автор: nh3000
Источник [7]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/java/64766
Ссылки в тексте:
[1] ссылка на сайт: https://developer.mozilla.org/ru/docs/Rhino/Download_Rhino
[2] https://developer.mozilla.org/ru/docs/Rhino: https://developer.mozilla.org/ru/docs/Rhino
[3] http://ru.wikipedia.org/wiki/Rhino: http://ru.wikipedia.org/wiki/Rhino
[4] https://developer.mozilla.org/en-US/docs/Rhino_documentation: https://developer.mozilla.org/en-US/docs/Rhino_documentation
[5] http://tool.oschina.net/uploads/apidocs/rhino/: http://tool.oschina.net/uploads/apidocs/rhino/
[6] https://github.com/andrew-medvedev/rhino-example: https://github.com/andrew-medvedev/rhino-example
[7] Источник: http://habrahabr.ru/post/229247/
Нажмите здесь для печати.