Статьи

Удовольствие от JavaScript на JVM

[Эта статья была написана Крисом Мауфортом]

Легко увидеть, как JavaScript везде в наши дни. Барьер для входа чрезвычайно низок; любой, у кого есть браузер, может написать и оценить его, а благодаря таким усовершенствованиям, как  Google V8 , написание JS на стороне сервера стало жизнеспособным предложением. Поэтому легко забыть, что Rhino — один из  оригинальных  интерпретаторов JavaScript, был написан на Java. Более того, Mozilla по-  прежнему присматривает  за почтенной базой кода, и вариант все еще входит в состав каждой среды выполнения Java. В то время как Rhino получил несколько новых  условий жизни , последняя и лучшая версия не поставляется с современными JVM. весело-с JavaScript-на-JVM

К тому же, 

солнце
 Oracle решила использовать серверную часть JS с собственной разработкой,  Nashorn , совершенно новым интерпретатором, встроенным в Java 8, который с самого начала был разработан для использования преимуществ  улучшений  на уровне байт-кода в последних JRE. Он также поставляется с поддержкой ECMAScript 5 (6 находится в стадии разработки), поддержкой модулей CommonJS и другими функциями, которые разработчики node.js принимают как должное. Уровень совместимости узла  также находится под сильным развитием.

Внутри Java-процесса вы можете получить экземпляр nashorn следующим образом:

ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
// and the requisite hello world:
engine.eval("print('Hello World!');");

Также возможно требовать внешних модулей JS, которые выборочно предоставляют части своих API:

// greeter.js
var sayHi = function(name) {
  return "Hello, " + name;
}
var somePrivateFunction = function() {
  return 'not gonna happen';
}
module.exports.sayHi = sayHi;

И из другого файла JS в том же каталоге:

var greeter = require('./greeter.js');
greeter.sayHi('Chris');
// => 'Hello, Chris';
greeter.somePrivateFunction();
// not accessible- throws an exception

Модульность удобна, но не дико захватывающая. Что более интересно, так это взаимодействие между языками — т. Е. Ваш JS-код может расширять и вызывать объекты Java и наоборот, например так:

var Callable = Java.type('java.util.concurrent.Callable');
var Work = Java.extend(Callable, {
  call: function() {
    return "Hello, Callable!";
  }
};
var myWork = new Work()
myWork.call(); // 'Hello, Callable!'

Чтобы проиллюстрировать, как это может быть эффективно использовано, давайте покажем реальный пример использования: написание логики маршрутизации для  Zuul  на JavaScript. Zuul — это балансировщик нагрузки с открытым исходным кодом, написанный Netflix и активно используемый на периферии их сервисов. Его основная привлекательность по сравнению с такими компонентами, как ELB, HAProxy и т. Д., Заключается в возможности произвольно сложным образом декорировать, фильтровать и перенаправлять трафик без каких-либо простоев. Логика маршрутизации также теоретически может быть написана на любом языке, работающем на JVM (код, который Netflix обнародовал, в основном использует Groovy), поэтому нам должно быть достаточно легко создать реализацию JavaScript

Первое, что нам нужно сделать, это реализовать   интерфейс DynamicCodeCompiler , взяв за основу реализацию Groovy от Netflix   . DynamicCodeCompiler — это та часть, которая придает Zuul большую часть своей ценности — беря исходные файлы, которые он обнаруживает, по заранее заданному пути и интерпретируя их на лету:

package com.logentries.api;

import com.netflix.zuul.DynamicCodeCompiler;
import jdk.internal.dynalink.beans.StaticClass;
import jdk.nashorn.api.scripting.NashornScriptEngine;
import javax.script.ScriptEngineManager;
import java.io.File;
import java.nio.file.Files;

public class JSCompiler implements DynamicCodeCompiler {

    private static final NashornScriptEngine ENGINE = (NashornScriptEngine) new ScriptEngineManager().getEngineByName("nashorn");

    @Override
    public Class compile(String sCode, String sName) throws Exception {
        Object obj = ENGINE.eval(sCode);
        // slightly naughty- StaticClass lives in an internal package
        StaticClass klass = (StaticClass) obj;
        return klass.getRepresentedClass();
    }

    @Override
    public Class compile(File file) throws Exception {
        String sourceCode = new String(Files.readAllBytes(file.toPath()));
        return compile(sourceCode, null);
    }
}

Далее давайте добавим фильтр — мы возьмем пример проекта Netflix,   который просто перенаправляет запросы на домашнюю страницу организации Apache, и позаимствовали вдохновение из фильтров Groovy, которые выходят из коробки. Давайте переопределим простой фильтр предварительной маршрутизации, который добавляет информацию об удаленном хосте в журнал отладки. Во-первых, нам нужно импортировать несколько классов, которые поставляются с базовой библиотекой Zuul:

// import a single type
var ZuulFilter = Java.type('com.netflix.zuul.ZuulFilter');
// we can also import a whole package and use it in a restricted scope
var contextImports = new JavaImporter(com.netflix.zuul.context);

Далее нам нужно расширить  ZuulFilter  нашей реализацией:

var MyJSDebugFilter = Java.extend(ZuulFilter, {
  filterType: function() {
    return "pre";
  },
  filterOrder: function() {
    return 10000;
  },
  shouldFilter: function() {
    true;
  },
  run: function() {
    // classes and interfaces inside contextImports only
    // live inside the scope of the block below
    with (contextImports) {
      // get the current request
      var req = RequestContext.currentContext().request();
      // add the remote address to the debug log
      Debug.addRequestDebug("REQUEST:: " + req.getScheme() + " " + req.getRemoteAddr() + ":" + req.getRemotePort());
      Debug.addRequestDebug("REQUEST:: > " + req.getMethod() + " " + req.getRequestURI() + " " + req.getProtocol());
    }
    return null;
  }
});

Для справки, оригинальная реализация Groovy может быть найдена  здесь . Обратите внимание, что  классы Debug  и  RequestContext видны только внутри   блока with () {} .

Вывод

Технические инновации, такие как Nashorn, показывают, что вы не привязаны к написанию Java, когда у вас большие инвестиции в JVM; если более выразительный язык или структура больше подходят для текущей работы, ничто не мешает вам использовать его, в то же время используя существующий код, чем мы пользуемся здесь, в  Logentries.