14
October
2008

Скриптовый движок в Java (Script Engine)

Posted in: Java technologies, J2SE |

Вот скажите, где могут выполняются скрипты написанные на javascript: на сервере, на клиенте? На клиенте, говорите? Это устаревшая информация…

С введением в Java версии 1.6 Scripting API стало очень просто и удобно писать extension поинты и динамические конфигурации для серверной логики.

В этой статье я хочу коротко рассказать вам о ScriptEngine, ну и еще кое о чем.

В частности хочу сделать следующее:

  • На небольшом примере показать, как просто использовать скриптовый движок, встроенный в Java
  • Провести небольшой бенчмаркинг и убедить всех, что скрипты - это не так уж и медленно
  • Неявно рассказать, что JavaScript - один из самых мощных (и недооцененных) языков программирования :)

Работа со скриптовыми движками производится через интерфейс ScriptEngine. В JDK есть пока что только одна имплементация этого интерфейса - RhinoScriptEngine.

Сразу небольшой пример кода:

public static void main(String[] args) throws ScriptException {
    ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");
    String helloWorld = (String) engine.eval("'Hello' + ' World!'");
    System.out.println(helloWorld);
}

Мы создаем ScriptEngine через ScriptEngineManager для языка JavaScript. Далее мы выполняем скрипт, суть которого - сконкатенировать 2 строки.
Как и во многих скриптовых языках, результатом вычисления некоторого куска кода является результат выполнения его последнего оператора.
То есть результатом нашего кода будет строка - “Hello World !”.

В одном проекте перед нашей командой возникла задача - создать механизм для гибкой маршрутизации СМС сообщений в зависимости от разных свойств (дата создания, оператор, номер назначения и т.д.).
Решено было попробовать движок, который встроен в JDK. Естественно, перед применением новой технологии необходимо проверить ее производительность. Ниже привожу код, который показывает, на что способен движок Rhino (код незначительно упрощен для улучшения понимания):

import javax.script.*;

public class ScriptBenchmarks {
    static String script = "sms.serviceNumber == '777' && sms.mobileNumber.startsWith('8050')";
    static Sms sms = new Sms();
    static int loops = 10000;

    static void init () {
        sms.setMobileNumber("80500000000");
        sms.setServiceNumber("777");
    }

    // testInitAndEvalInsideLoop average: 1ms
    static void testInitAnEvalInsideLoop() throws ScriptException {
        long start = System.currentTimeMillis();
        for (int i = 0; i < loops; i++) {
            ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");
            engine.put("sms", sms);
            engine.eval(script);
        }
        long total = System.currentTimeMillis() - start;
        System.out.println("testInitAnEvalInsideLoop average: " + ((float)total / loops));
    }

    // testInitOutsideEvalInsideLoop average: 0.1ms
    static void testInitOutsideEvalInsideLoop() throws ScriptException {
        ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");
        long start = System.currentTimeMillis();
        for (int i = 0; i < loops; i++) {
            engine.put("sms", sms);
            engine.eval(script);
        }
        long total = System.currentTimeMillis() - start;
        System.out.println("testInitOutsideEvalInsideLoop average: " + ((float)total / loops));
    }

    // testPrecompiledScript average: 0.05ms - 0.1ms
    static void testPrecompiledScript() throws ScriptException {
        ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");
        CompiledScript compiledScript = ((Compilable) engine).compile(script);
        long start = System.currentTimeMillis();
        for (int i = 0; i < loops; i++) {
            engine.put("sms", sms);
            compiledScript.eval();
        }
        long total = System.currentTimeMillis() - start;
        System.out.println("testPrecompiledScript average: " + ((float)total / loops));
    }

    static class Sms {
        String mobileNumber;
        String serviceNumber;

        public String getMobileNumber() { return mobileNumber; }
        public void setMobileNumber(String mobileNumber) { this.mobileNumber = mobileNumber; }

        public String getServiceNumber() { return serviceNumber; }
        public void setServiceNumber(String serviceNumber) { this.serviceNumber = serviceNumber; }
    }

    public static void main(String[] args) throws ScriptException {
        init();

        testInitAnEvalInsideLoop();
        testInitOutsideEvalInsideLoop();
        testPrecompiledScript();
    }
}

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

Надеюсь, статья была вам полезна. Если когда-нибудь вам придется предоставить API для гибкого конфигурирования заказчиком какой-либо логики, вспомните о том, что в JDK уже есть все, что вам нужно.

На этом, пожалуй, и закончу свое повествование. Жду вопросов и замечаний. Всего вам хорошего.

11 Comments »

RSS feed for comments on this post.



October 15, 2008 #

К первым абзацам. В Apache Cocoon еще лет 5 назад была возможность писать server side JavaScript для описания последовательностей страниц на сайтах (page flow).

А вот нужно ли такое в JDK включать - спорный момент.

jekyll
October 20, 2008 #

Возможность пользоваться JS внутри Java это очень здорово! Ведь JS знает куда больше людей, чем тот же Python.

pro100sanya
October 23, 2008 #

Вот допустим используем мы Velocity Script engine.
И допустим вызываем мы метод eval().

А в качестве параметра - “”.

И вот eval возвращает null, и выводит этот скрипт в stdout. Хотя я ожидал что eval эту строку вернет..

Но стоит добавить директиву #parse() в ту строку, как eval обрабатывает скрипт нужным мне образом и возвращает результат.

Как по мне, так весьма странное поведение.
Есть ли какой то способ обойти эту проблему?

pro100sanya
October 23, 2008 #

ой.. тэги хтмл не прошли.
Вообщем та строка что в параметры передовалась это были обычные теги хтмл. начало и конец.

[хмтл][/хтмл]

Hain
December 16, 2008 #

Вопрос к Косте и компании
У меня возникла необходимость создания нескольких проектов
Я не программер ,работаю инженером-электронщиком ,шеф подгрузил т.к. имею некоторый опыт разработки PHP\MySQL но к сожалению он не приеним ,стоит именно задача :Java фэйс ввиду использования Java терменалов на клиентской стороне -и оупенсоурсная база данных сзади .
Учил Java сам по книжкам ,делал примеры ,то есть знаком с принципами ООП в Java,основами разработки GUI ,принципами JDBC ,но опыта разработки конкретных проектов на Java,тянущих на право практического использования не имею .Поскольку возникла необходимость написать ,анализ чужого проекта был бы идеальным началом для написания своего

Буду очень благодарен ,за возможность взглянуть на профессионально
написанный код базы Java в связке MySQL(придпочтительно ввиду знакомости) либо PostgreSQL,желателен
-хорошо комментированный код
-графическая часть на Swing желательно писанная руками ,для простоты анализа (но не обязательно )
-в этом качестве пойдет любой законченный опен-соурс Java проект ,а лучше если кто покажет свой проект ,в этом случае я был бы не лишен возможности задать автору возникшие вопросы..

PS : В нэте я погуглил но ничего подходящего не нашел ,цельные ,готовые рещения не попадались

December 19, 2008 #

Ответил Вам на почту

Dilshod Davlatov
February 6, 2009 #

сразу говорю я не профи, просто хочу знать можно ли написать скрипт комментария на яваскрипт вот такой в котором я отправил этот комментарий?

Benchmark ninja
February 18, 2009 #

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

Andrey
July 3, 2009 #

Добрый день. В принципе язык удобный и легкий для понимания, но возникали проблемы в скорости разбора xml… разбирая xml с помощью методов Rhino каким-то непонятным образом увеличивалось время на обработку запроса, пришлось переписывать через dom на java, стало в 2 раза быстрее.

Pavel
July 11, 2009 #

Спасибо огромное за статью… Выричил.

Timur
March 2, 2010 #

А Groovy таким образом можно подключить?

Leave a comment

XHTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong> <pre>