Javenue logo

Javenue

Программирование на Java

Информационные технологии

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

Вот скажите, где могут выполняются скрипты написанные на 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 уже есть все, что вам нужно.

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



Комментариев: 0

  Выйти

  * для публикации комментариев нужно