[Эта статья была написана Крисом Мауфортом]
Легко увидеть, как JavaScript везде в наши дни. Барьер для входа чрезвычайно низок; любой, у кого есть браузер, может написать и оценить его, а благодаря таким усовершенствованиям, как Google V8 , написание JS на стороне сервера стало жизнеспособным предложением. Поэтому легко забыть, что Rhino — один из оригинальных интерпретаторов JavaScript, был написан на Java. Более того, Mozilla по- прежнему присматривает за почтенной базой кода, и вариант все еще входит в состав каждой среды выполнения Java. В то время как Rhino получил несколько новых условий жизни , последняя и лучшая версия не поставляется с современными 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.
