[Эта статья была написана Крисом Мауфортом]
Легко увидеть, как 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.