Статьи

Поддержка динамических языков

Эта статья является частью нашего Академического курса под названием Advanced Java .

Этот курс призван помочь вам наиболее эффективно использовать Java. В нем обсуждаются сложные темы, включая создание объектов, параллелизм, сериализацию, рефлексию и многое другое. Он проведет вас через ваше путешествие в мастерство Java! Проверьте это здесь !

1. Введение

В этой части руководства наше внимание будет полностью сосредоточено на поддержке сценариев и динамических языков в Java. Начиная с Java 7, JVM имеет прямую поддержку современных динамических (также называемых сценариями) языков, а выпуск Java 8 привнес еще больше улучшений в это пространство.

Одной из сильных сторон динамических языков является то, что поведение программы определяется во время выполнения, а не во время компиляции. Среди этих языков Ruby ( https://www.ruby-lang.org/en/ ), Python ( https://www.python.org/ ) и JavaScript ( http://en.wikipedia.org/wiki/ JavaScript ) приобрели большую популярность и являются наиболее широко используемыми в настоящее время. Мы рассмотрим, как API сценариев Java открывает путь для интеграции этих языков в существующие приложения Java.

2. Поддержка динамических языков

Как мы уже хорошо знаем, Java является языком статической типизации. Это означает, что вся типизированная информация для класса, его членов, параметров метода и возвращаемых значений доступна во время компиляции. Используя все эти детали, компилятор Java испускает строго типизированный байт-код, который затем может быть эффективно интерпретирован JVM во время выполнения.

Однако динамические языки выполняют проверку типов во время выполнения, а не во время компиляции. Задача работы с динамическими языками состоит в том, как реализовать систему времени выполнения, которая может выбрать наиболее подходящую реализацию метода для вызова после компиляции программы.

Долгое время у JVM не было специальной поддержки динамически типизированных языков. Но в выпуске Java 7 появилась новая команда invokedynamic, которая позволила системе времени выполнения (JVM) настроить связь между сайтом вызова (местом, где вызывается метод) и реализацией метода. Это действительно открыло двери для эффективной поддержки и реализации динамических языков на платформе JVM.

3. Скриптовый API

В рамках выпуска Java 6 в 2006 году новый пакетный API был представлен в пакете javax.script . Этот расширяемый API был разработан для подключения в основном любого языка сценариев (который обеспечивает реализацию механизма сценариев) и запуска его на платформе JVM.

На самом деле API сценариев Java очень маленький и довольно простой. Начальным шагом для начала работы с API сценариев является создание нового экземпляра класса ScriptEngineManager . ScriptEngineManager предоставляет возможность обнаруживать и извлекать доступные механизмы сценариев по их именам из пути к классу запущенного приложения.

Каждый механизм сценариев представлен с использованием соответствующей реализации ScriptEngine и, по существу, предоставляет возможность выполнять сценарии с использованием семейства функций eval() (которое имеет несколько перегруженных версий). Многие популярные скриптовые (динамические) языки уже предоставляют поддержку API сценариев Java, и в следующих разделах этого руководства мы увидим, насколько хорошо этот API работает на практике, играя с JavaScript , Groovy , Ruby / JRuby и Python / Jython. ,

4. JavaScript на JVM

Не случайно мы начнем наше путешествие с языка JavaScript, поскольку это был один из первых языков сценариев, поддерживаемых API сценариев стандартной библиотеки Java. А также потому, что, по большому счету, это единственный язык программирования, который понимает каждый веб-браузер.

В своей простейшей форме функция eval() выполняет сценарий, передаваемый ему в виде простой строки Java. Сценарий не имеет общего состояния с оценщиком (или вызывающей стороной) и является отдельным фрагментом кода. Однако в типичных реальных приложениях это довольно редко, и чаще всего требуется, чтобы некоторые переменные или свойства предоставлялись сценарию для выполнения значимых вычислений или действий. С учетом вышесказанного давайте рассмотрим краткий пример оценки реального вызова функции JavaScript с использованием простых привязок переменных:

1
2
3
4
5
6
7
8
final ScriptEngineManager factory = new ScriptEngineManager();
final ScriptEngine engine = factory.getEngineByName( "JavaScript" );
         
final Bindings bindings = engine.createBindings();
bindings.put( "str", "Calling JavaScript" );
bindings.put( "engine", engine );
         
engine.eval( "print(str + ' from ' + engine.getClass().getSimpleName() )", bindings );

После выполнения на консоли будет напечатан следующий вывод:

1
Calling JavaScript from RhinoScriptEngine

Долгое время Rhino был единственным скриптовым движком JavaScript, доступным в JVM. Но в версии Java 8 появилась новая реализация скриптового движка JavaScript под названием Nashorn ( http://www.oracle.com/technetwork/articles/java/jf14-nashorn-2126515.html ).

С точки зрения API, не так много различий, однако внутренняя реализация значительно отличается, обещая гораздо лучшую производительность. Вот тот же пример, переписанный для использования движка Nashorn JavaScript:

1
2
3
4
5
6
7
final ScriptEngineManager factory = new ScriptEngineManager();
final ScriptEngine engine = factory.getEngineByName( "Nashorn" );
         
final Bindings bindings = engine.createBindings();
bindings.put( "engine", engine );
         
engine.eval( "print(str + ' from ' + engine.getClass().getSimpleName() )", bindings );

Следующий вывод будет напечатан на консоли (на этот раз обратите внимание на другую реализацию скриптового движка):

1
Calling JavaScript from NashornScriptEngine

Тем не менее, примеры фрагментов кода JavaScript, которые мы рассмотрели, довольно тривиальны. На самом деле вы можете оценить целые файлы JavaScript, используя перегруженный вызов функции eval (), и реализовать довольно сложные алгоритмы, чисто на JavaScript. В следующих разделах мы увидим такие примеры при изучении других языков сценариев.

5. Отличный на JVM

Groovy ( http://groovy.codehaus.org ) является одним из наиболее успешных динамических языков для платформы JVM. Он часто используется бок о бок с Java, однако он также обеспечивает реализацию движка API сценариев Java и может использоваться аналогично JavaScript.

Давайте сделаем этот пример Groovy более осмысленным и интересным, разработав небольшой автономный скрипт, который выводит на консоль некоторые подробности о каждой книге из коллекции, которой она поделилась, вызывая приложение Java.

Класс Book довольно прост и имеет только два свойства: author и title:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
public class Book {
    private final String author;
    private final String title;
     
    public Book(final String author, final String title) {
        this.author = author;
        this.title = title;
    }
 
    public String getAuthor() {
        return author;
    }
 
    public String getTitle() {
        return title;
    }
}

Скрипт Groovy (называемый просто script.groovy ) использует некоторые изящные языковые функции, такие как замыкания и интерполяция строк, для вывода свойств книги на консоль:

1
2
3
4
5
6
books.each {
    println "Book '$it.title' is written by $it.author"
}
 
println "Executed by ${engine.getClass().simpleName}"
println "Free memory (bytes): " + Runtime.getRuntime().freeMemory()

Теперь давайте выполним этот скрипт Groovy, используя API сценариев Java и предопределенную коллекцию книг (конечно, все о Groovy ):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
final ScriptEngineManager factory = new ScriptEngineManager();
final ScriptEngine engine = factory.getEngineByName( "Groovy" );
         
final Collection< Book > books = Arrays.asList(
    new Book( "Venkat Subramaniam", "Programming Groovy 2" ),
    new Book( "Ken Kousen", "Making Java Groovy" )
    );
                 
final Bindings bindings = engine.createBindings();
bindings.put( "books", books );
bindings.put( "engine", engine );
         
try( final Reader reader = new InputStreamReader(
        Book.class.getResourceAsStream("/script.groovy" ) ) ) {
    engine.eval( reader, bindings );       
}

Обратите внимание, что обработчик сценариев Groovy имеет полный доступ к стандартной библиотеке Java и не требует каких-либо дополнительных привязок. Чтобы подтвердить это, последняя строка из вышеописанного скрипта Groovy обращается к текущей среде выполнения, вызывая статический метод Runtime.getRuntime() и распечатывает объем свободной кучи, доступной для запуска JVM (в байтах). Следующий пример вывода появится на консоли:

1
2
3
4
Book 'Programming Groovy 2' is written by Venkat Subramaniam
Book 'Making Java Groovy' is written by Ken Kousen
Executed by GroovyScriptEngineImpl
Free memory (bytes): 153427528

Прошло 10 лет с момента появления Groovy . Он быстро стал очень популярным благодаря инновационным языковым возможностям, сходным с синтаксисом Java и отличной совместимостью с существующим кодом Java. Может показаться, что введение лямбда-кода и Stream API в Java 8 сделало Groovy менее привлекательным выбором, однако он все еще широко используется разработчиками Java.

6. Рубин на JVM

Пару лет назад Ruby ( https://www.ruby-lang.org/en/ ) был самым популярным динамическим языком, используемым для разработки веб-приложений. Несмотря на то, что в настоящее время его популярность несколько затенена, Ruby и его экосистема внесли много инноваций в разработку современных веб-приложений, вдохновив на создание и развитие многих других языков программирования и сред.

JRuby ( http://jruby.org/ ) — это реализация языка программирования Ruby для платформы JVM. Подобно Groovy , он также обеспечивает отличную совместимость с существующим кодом Java, сохраняя красоту синтаксиса языка Ruby .

Давайте перепишем скрипт Groovy из раздела Groovy для JVM на языке Ruby (с именем script.jruby ) и оценим его с помощью API сценариев Java.

1
2
3
4
5
6
7
$books.each do |it|
    java.lang.System.out.println( "Book '" + it.title + "' is written by " + it.author )
end
 
java.lang.System.out.println( "Executed by " + $engine.getClass().simpleName )
java.lang.System.out.println( "Free memory (bytes): " +  
        java.lang.Runtime.getRuntime().freeMemory().to_s )

Коды оценки сценариев остаются в основном такими же, за исключением другого механизма сценариев и коллекции образцов книг, которая теперь полностью посвящена Ruby .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
final ScriptEngineManager factory = new ScriptEngineManager();
final ScriptEngine engine = factory.getEngineByName( "jruby" );
         
final Collection< Book > books = Arrays.asList(
    new Book( "Sandi Metz", "Practical Object-Oriented Design in Ruby" ),
    new Book( "Paolo Perrotta", "Metaprogramming Ruby 2" )
    );
                 
final Bindings bindings = engine.createBindings();
bindings.put( "books", books );
bindings.put( "engine", engine );
         
try( final Reader reader = new InputStreamReader(
        Book.class.getResourceAsStream("/script.jruby" ) ) ) {
    engine.eval( reader, bindings );       
}

Следующий пример вывода появится на консоли:

1
2
3
4
Book 'Practical Object-Oriented Design in Ruby' is written by Sandi Metz
Book 'Metaprogramming Ruby 2' is written by Paolo Perrotta
Executed by JRubyEngine
Free memory (bytes): 142717584

Как мы можем понять из приведенного выше фрагмента кода JRuby , использование классов из стандартной библиотеки Java немного многословно и должно начинаться с префикса по имени пакета (есть некоторые приемы, чтобы избавиться от этого, но мы не будем вдаваться в такие конкретные детали). ).

7. Python на JVM

Последний, но не менее важный пример — демонстрация реализации языка Python ( https://www.python.org/ ) на платформе JVM, которая называется Jython ( http://www.jython.org/ ).

Язык Python в последнее время приобрел большую популярность, и его популярность растет с каждым днем. Он широко используется научным сообществом и имеет большой набор библиотек и структур, от веб-разработки до обработки естественного языка.

Следуя тому же пути, что и в Ruby , мы собираемся переписать пример сценария из Groovy в разделе JVM с использованием языка Python (с именем script.py ) и оценить его с помощью API сценариев Java.

1
2
3
4
5
6
7
from java.lang import Runtime
 
for it in books:
    print "Book '%s' is written by %s" % (it.title, it.author)
 
print "Executed by " + engine.getClass().simpleName
print "Free memory (bytes): " + str( Runtime.getRuntime().freeMemory() )

Давайте создадим экземпляр механизма сценариев Jython и выполним приведенный выше сценарий Python, используя уже знакомый API сценариев Java.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
final ScriptEngineManager factory = new ScriptEngineManager();
final ScriptEngine engine = factory.getEngineByName( "jython" );
         
final Collection< Book > books = Arrays.asList(
        new Book( "Mark Lutz", "Learning Python" ),
        new Book( "Jamie Chan", "Learn Python in One Day and Learn It Well" )
    );
                 
final Bindings bindings = engine.createBindings();
bindings.put( "books", books );
bindings.put( "engine", engine );
         
try( final Reader reader = new InputStreamReader(
        Book.class.getResourceAsStream("/script.py" ) ) ) {
    engine.eval( reader, bindings );       
}

Следующий пример вывода будет напечатан на консоли:

1
2
3
4
Book 'Learning Python' is written by Mark Lutz
Book 'Learn Python in One Day and Learn It Well' is written by Jamie Chan
Executed by PyScriptEngine
Free memory (bytes): 132743352

Сила Python как языка программирования заключается в его простоте и крутой кривой обучения. С целой армией разработчиков Python возможность интеграции языка сценариев Python в ваши Java-приложения как некий механизм расширения может показаться интересной идеей.

8. Использование скриптового API

API сценариев Java — отличный способ обогатить ваши приложения Java расширяемой поддержкой сценариев, просто выберите свой язык. Это также самый простой способ подключить предметно-ориентированные языки (DSL) и позволяет бизнес-экспертам выразить свои намерения наиболее удобным образом.

Последние изменения в самой JVM (см. Раздел « Поддержка динамических языков ») сделали ее более дружественной платформой времени выполнения для различных реализаций динамических языков (сценариев). Вне всякого сомнения, в будущем будет доступно все больше и больше скриптовых движков, открывающих двери для бесшовной интеграции с новыми и существующими Java-приложениями.

9. Что дальше

Начиная с этой части, мы действительно начинаем обсуждение продвинутых концепций Java как языка и JVM как отличной платформы исполнения во время выполнения. В следующей части руководства мы рассмотрим API компилятора Java и API дерева компиляторов Java, чтобы узнать, как манипулировать источниками Java во время выполнения.

9. Скачать код

Это был урок по поддержке динамического языка , часть 12 курса Advanced Java . Вы можете скачать исходный код здесь: advanced-java-part-12