JavaScript w Javie

Niedawny post JavaLobby Top 10 nieużywanych funkcji w Javie był niezwykle popularny. W chwili pisania tego tekstu jest to najwyżej oceniany post w kategorii DZone Top Links. Ponadto została również wysłana odpowiedź. W obu postach na blogach jest wiele interesujących obserwacji na temat niewykorzystywanych funkcji w Javie i zgadzam się z niektórymi bardziej niż innymi. Jednak rzeczą, która naprawdę zwróciła moją uwagę, było stwierdzenie, że Java SE 6 jest jedną z najbardziej nieużywanych funkcji Java.

Bardzo lubię pracować z Javą SE 6 i kilka razy pisałem o funkcjach Java SE 6 lub pisałem o nich na blogu. W tym poście na blogu zamierzam zademonstrować część możliwości Java SE 6 w zakresie hostowania kodu JavaScript.

Większość programistów Java i programistów JavaScript rozumie, że poza czterema literami „JAVA”, JavaScript i Java mają niewiele wspólnego poza pewnym dziedzictwem podobnym do języka C. Mimo to czasami może być przydatne uruchomienie języka skryptowego z poziomu kodu Java, a Java SE 6 na to pozwala.

Pakiet javax.script został wprowadzony wraz z Javą SE 6 i zawiera klasy, interfejsy oraz sprawdzony wyjątek związany z używaniem silników skryptowych w Javie. Ta publikacja na blogu będzie koncentrować się na ScriptEngineFactory, ScriptEngineManager, ScriptEngine i ScriptException.

Jedną z pierwszych rzeczy, które warto zrobić, jest określenie, które silniki skryptów są już dostępne. Następny fragment kodu pokazuje, jak łatwo można to zrobić w Javie SE 6.

final ScriptEngineManager manager = new ScriptEngineManager(); for (final ScriptEngineFactory scriptEngine : manager.getEngineFactories()) { System.out.println( scriptEngine.getEngineName() + " (" + scriptEngine.getEngineVersion() + ")" ); System.out.println( "\tLanguage: " + scriptEngine.getLanguageName() + "(" + scriptEngine.getLanguageVersion() + ")" ); System.out.println("\tCommon Names/Aliases: "); for (final String engineAlias : scriptEngine.getNames()) { System.out.println(engineAlias + " "); } } 

Kod pokazany powyżej generuje dane wyjściowe, takie jak pokazane na następnej migawce ekranu.

Jak pokazuje ten obraz, silnik JavaScript Mozilla Rhino jest zawarty w Javie SE 6. firmy Sun. Widzimy również pewne „popularne nazwy”, które są powiązane z tym konkretnym silnikiem. Do wyszukiwania tego silnika można użyć dowolnej z tych nazw. W dalszych przykładach w tym poście będę używał nazwy zwyczajowej „js” do tego wyszukiwania.

Następny przykładowy kod będzie korzystał z dostarczonego silnika Rhino JavaScript, aby wykonać część kodu JavaScript z kodu Java. W tym przypadku będziemy korzystać z funkcji toExponential JavaScript.

 /** * Write number in exponential form. * * @param numberToWriteInExponentialForm The number to be represented in * exponential form. * @param numberDecimalPlaces The number of decimal places to be used in the * exponential representation. */ public static void writeNumberAsExponential( final Number numberToWriteInExponentialForm, final int numberDecimalPlaces) { final ScriptEngine engine = manager.getEngineByName("js"); try { engine.put("inputNumber", numberToWriteInExponentialForm); engine.put("decimalPlaces", numberDecimalPlaces); engine.eval("var outputNumber = inputNumber.toExponential(decimalPlaces);"); final String exponentialNumber = (String) engine.get("outputNumber"); System.out.println("Number: " + exponentialNumber); } catch (ScriptException scriptException) { LOGGER.severe( "ScriptException encountered trying to write exponential: " + scriptException.toString()); } } 

Powyższy kod bezpośrednio wywołuje JavaScript przy użyciu metody ScriptEngine.eval (String) w celu oszacowania podanego ciągu znaków zawierającego składnię JavaScript. Przed wywołaniem evalmetody dwa parametry są „przekazywane” (wiązane) do kodu JavaScript za pośrednictwem wywołań ScriptEngine.put (String, Object). Obiekt wynikowy wykonanego JavaScript jest dostępny w kodzie Java za pomocą wywołania ScriptEngine.get (String).

Aby zademonstrować powyższy kod za pomocą toExponentialfunkcji, użyję następującego kodu „klienta”.

final int sourceNumber = 675456; writeNumberAsExponential(sourceNumber, 1, System.out); writeNumberAsExponential(sourceNumber, 2, System.out); writeNumberAsExponential(sourceNumber, 3, System.out); writeNumberAsExponential(sourceNumber, 4, System.out); writeNumberAsExponential(sourceNumber, 5, System.out); 

Gdy powyższy kod zostanie uruchomiony w odniesieniu do metody writeNumberAsExponential pokazanej wcześniej i zostanie zastosowany JavaScript, dane wyjściowe będą wyglądać podobnie do pokazanych na następnej migawce ekranu.

Ten przykład wystarczy, aby zademonstrować, jak łatwo jest wywołać funkcjonalność JavaScript z poziomu Java SE 6. Jednak można to zaimplementować jeszcze bardziej ogólnie, co pokażą dwa następne przykłady. Pierwszy przykład pokazuje wywołanie względnie dowolnego kodu JavaScript bez przekazanych / powiązanych parametrów, a drugi przykład przedstawia wywołanie względnie dowolnego kodu JavaScript z przekazanymi / powiązanymi parametrami.

Stosunkowo dowolny ciąg JavaScript może być przetwarzany za pomocą kodu podobnego do pokazanego poniżej.

 /** * Process the passed-in JavaScript script that should include an assignment * to a variable with the name prescribed by the provided nameOfOutput and * may include parameters prescribed by inputParameters. * * @param javaScriptCodeToProcess The String containing JavaScript code to * be evaluated. This String is not checked for any type of validity and * might possibly lead to the throwing of a ScriptException, which would * be logged. * @param nameOfOutput The name of the output variable associated with the * provided JavaScript script. * @param inputParameters Optional map of parameter names to parameter values * that might be employed in the provided JavaScript script. This map * may be null if no input parameters are expected in the script. */ public static Object processArbitraryJavaScript( final String javaScriptCodeToProcess, final String nameOfOutput, final Map inputParameters) { Object result = null; final ScriptEngine engine = manager.getEngineByName("js"); try { if (inputParameters != null) { for (final Map.Entry parameter : inputParameters.entrySet()) { engine.put(parameter.getKey(), parameter.getValue()); } } engine.eval(javaScriptCodeToProcess); result = engine.get(nameOfOutput); } catch (ScriptException scriptException) { LOGGER.severe( "ScriptException encountered trying to write arbitrary JavaScript '" + javaScriptCodeToProcess + "': " + scriptException.toString()); } return result; } 

Powyższy kod zapewnia dużą elastyczność w zakresie kodu JavaScript, który można przetwarzać. Prawdopodobnie nie jest to najlepszy pomysł na kod produkcyjny, ale ułatwia zademonstrowanie użycia różnych funkcji JavaScript w Javie.

Pierwszy przykład użycia tego względnie dowolnego przetwarzania JavaScript wykorzystuje obiekt Date JavaScript. Przykładowy kod jest pokazany obok.

 System.out.println( "Today's Date: " + processArbitraryJavaScript( "var date = new Date(); var month = (date.getMonth()+1).toFixed(0)", "month", null) + "/" + processArbitraryJavaScript( "var date = new Date(); var day = date.getDate().toFixed(0)", "day", null) + "/" + processArbitraryJavaScript( "var date = new Date(); var year = date.getFullYear().toFixed(0)", "year", null) ); 

Ten kod określa, że ​​należy pobrać datę JavaScript (która będzie datą bieżącą), a miesiąc, dzień miesiąca i cały rok powinny zostać wyodrębnione z tej instancji Date. Wynik tego pojawia się jako następny.

Ostatni przykład działał na dowolnym łańcuchu JavaScript, ale nie używał żadnych parametrów. Następny przykład demonstruje dostarczanie parametrów do tego dowolnego przetwarzania ciągu JavaScript, ponieważ demonstruje użycie funkcji pow JavaScript. Kod dla tego przykładu jest wymieniony poniżej.

 final Map exponentParameters = new HashMap(); exponentParameters.put("base", 2); exponentParameters.put("exponent", 5); System.out.println( "2 to the 5 is: " + processArbitraryJavaScript( "var answer = Math.pow(base,exponent)", "answer", exponentParameters) ); 

Dane wyjściowe uruchomienia tego przykładu są pokazane na poniższej migawce ekranu.

Jako ostatni przykład tego posta na blogu pokazuję standardowe toString()wyjście ScriptExceptionzadeklarowane w niektórych z poprzednich przykładów. ScriptEngine.evalMetoda zgłasza to sprawdzony wyjątek, jeśli wystąpi błąd w wykonywaniu / oceny dostarczonego skryptu. Ta metoda zgłasza również NullPointerException, jeśli podany String ma wartość null. Kod używany do wymuszenia błędu skryptu jest wyświetlany obok.

 /** * Intentionally cause script handling error to show the type of information * that a ScriptException includes. */ public static void testScriptExceptionHandling() { System.out.println(processArbitraryJavaScript("Garbage In", "none", null)); } 

Ten kod zawiera nonsensowny skrypt (pod względem składni JavaScript), ale to jest dokładnie to, co jest potrzebne do zademonstrowania ScriptException.toString (), która jest wywoływana jako część obsługi wyjątków w metodzie pokazanej powyżej do obsługi dowolnego ciągu JavaScript . Kiedy kod jest wykonywany, widzimy informacje o wyjątku, jak pokazano na następnym obrazku.

Część danych wyjściowych, z której pochodzi, ScriptException.toString()to część, która stwierdza: „javax.script.ScriptException: sun.org.mozilla.javascript.internal.EvaluatorException: missing; przed instrukcją (nr 1) w wierszu nr 1”.

ScriptExceptionZawiera nazwę pliku, numer wiersza i numer kolumny wyjątku, co jest szczególnie przydatne, jeśli plik z kodem JavaScript jest przewidziane do oceny.

Wniosek

Java SE 6 ułatwia korzystanie z JavaScript w kodzie Java. Inne silniki skryptów również mogą być powiązane z Javą, ale warto mieć taki, który jest dostarczany od razu z Mozilla Rhino.

Pełny zrzut ekranu kodu i danych wyjściowych

Dla kompletności załączam tutaj pełną listę kodu w jednym miejscu i wynikowe dane wyjściowe po tym.

JavaScriptInJavaExample.java