Das Verhalten von JS auf Java SE 8 Nashorn änderte sich plötzlich

Was meinst du?

Es wäre lange her gewesen, die Einzelheiten der Angelegenheit zu schreiben, daher werde ich zuerst die Schlussfolgerung schreiben.

In der Java-Version "1.8.0_92" wurden Nashorn einige Korrekturen hinzugefügt, die das Verhalten von JavaScript auf Nashorn durch Aktualisierung von Java ändern können. Die Änderungen, die den größten Einfluss zu haben scheinen (und ich bin wirklich begeistert), sind:

*** Wenn Java Long Type und Int Type an von Nashorn ausgeführtes JavaScript übergeben werden, wurden sie in der Vergangenheit als "Number" -Typ behandelt, aber nach der Änderung werden sie als "Object" -Typ behandelt ***

Es gibt wahrscheinlich einige Muster im Code, die von dieser Änderung betroffen (verhaltensmäßig geändert) sind, aber ein typischer (eigentlich bin ich süchtig danach) führt strenge Gleichheitsvergleiche durch. Ich werde.

// JavaScript
function hoge(arg) { // <-arg ist ein Objekt, das die von Java übergebenen Argumente speichert
  if arg.getLongValue() === 0 return 0 // <-Hier arg.getLongValue()Ist 0L auf Java===Das Ergebnis von ist falsch und else wird ausgeführt
  else arg.getLongValue / 2;
}; 

Gegenmaßnahmen

Für die oben genannten Fälle gibt es zwei mögliche Gegenmaßnahmen.

  1. Ändern Sie die JavaScript-Seite. Stoppen Sie den strengen Gleichheitsvergleich (===) und wechseln Sie zum Gleichheitsvergleich (==) für den Teil, in dem der von Java übergebene numerische Wert (long, int) und der Number-Typ von JS verglichen werden. ..
  2. Ändern Sie die Java-Seite. Wandeln Sie die Zahlen (long, int), die an JavaScript übergeben werden sollen, in einen Doppeltyp um (nur ein Doppeltyp wird von JS als Zahlentyp behandelt).

Welche übernommen werden sollte, hängt von der Situation ab. Wenn Sie jedoch die Java-Seite ändern möchten, müssen Sie sie erstellen, bereitstellen und neu starten. In vielen Fällen denke ich, dass 1. einfacher ist.


Unten die Geschichte, auf die ich mich wirklich einließ

Was ist Nashorn überhaupt?

Eine JavaScript-Engine zum Ausführen von JavaScript in Java. In "Java SE 6-7" gab es eine Mozilla-JavaScript-Engine namens "Rhino", in "Java SE 8" wurde diese durch Oracle "Nashorn" ersetzt. Im Vergleich zu "Rhino" ist es mit "ECMAScript-262" kompatibel und die Ausführungsgeschwindigkeit wurde erheblich verbessert, was ein großer Unterschied zu sein scheint.

Was genau macht Sie glücklich, JS in Java ausführen zu können?

Ich denke, es gibt viele Verwendungszwecke für Nashorn, aber zum Beispiel kann es notwendig sein, "dynamisches Ersetzen von Logik" zu ermöglichen. In einem bestimmten Berechnungsprozess möchte ich so etwas wie einen AB-Test der Logik durchführen, um zu sehen, welche Art von Änderung in der Leistung auftreten wird, und wenn eine positive Änderung festgestellt wird, schrittweise zu einer neuen Logik übergehen. Aha.

Ich denke, die Erklärung ist abstrakt und schwer zu verstehen, aber kurz gesagt

// Java
Object a: = doSomething() // step 1

Object b = executeDynamicLogic(a) // step 2 <--Ich möchte es leicht ersetzen!

return b // step 3

Die Atmosphäre ist so.

Übergeben Sie im obigen Beispiel das Argument "a" an den aus DB usw. abgerufenen Skriptcode, führen Sie es aus und speichern Sie das Ergebnis in der Variablen "b". .. .. Ich möchte in der Lage sein, das Verhalten der Anwendung zu ändern, indem ich das in der Datenbank gespeicherte Skript AKTUALISIERE. Es ist, als würde man Logik als Daten behandeln.

Wenn Sie den Zweck erreichen können, können Sie Python oder Ruby anstelle von JavaScript verwenden, aber in meinem Fall

Unter diesem Gesichtspunkt erinnere ich mich, dass ich mich für JavaScript entschieden habe.

Und der Vorfall ist passiert

Irgendwann gab JavaScript auf Nashorn, das noch nie zuvor ein Problem hatte, einen Fehler aus.

Was war die Ursache?

Als ich anfing zu untersuchen, vermutete ich natürlich Änderungen in meinem Code, aber ich nahm keine Änderungen an Nashorn vor, und das ausgeführte Skript wurde auch an das Skript übergeben. An den Daten war nichts Verdächtiges. Als ich anfing zu denken, dass ich das nicht verstehe ...

** "Oh, vielleicht habe ich die Java-Version aktualisiert ..." **

Ein Lichtstrahl. Zu diesem Zeitpunkt begann ich an der Möglichkeit zu zweifeln, dass "Nashorn nicht von einigen Änderungen im Update betroffen ist?", Und als ich es nachschlug, fand ich es sehr schnell.

JDK-8144020 | Remove long as an internal numeric type

Zusammenfassend,

*** Bisher wurden "int" und "long" auch intern als "double" behandelt und der JS-Nummer zugeordnet (in Java ist die ECMA-ähnliche Definition von "number" erfüllt. (Da es sich nur um "double" handelt) Wenn Sie jedoch "long" als "double" behandeln, treten aufgrund der zusätzlichen Genauigkeit von 53 Bit einige Probleme auf. Beenden Sie also diesen internen Konvertierungsprozess YO! *** ***

.. .. .. "Ja wirklich?" Also, wenn ich die Betriebsumgebung überprüfe

$ java -version
java version "1.8.0_131"
Java(TM) SE Runtime Environment (build 1.8.0_131-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)

Hmmm. Es ist höher als "1.8.0_92". .. ..

Der Code, der das Problem verursacht hat

Der Code, der das Problem verursacht hat, war ungefähr so:

// JavaScript
function hoge(arg) { // <-arg ist ein Objekt, das die von Java übergebenen Argumente speichert
  if arg.getLongValue1() === 0 return 0
  else arg.getValue1() / arg.getLongValue2(); 
};

Der Punkt ist, dass wir versuchen, eine Nullteilung zu vermeiden, aber das Problem ist

arg.getValue1() === 0

Es ist der Teil von. Exakter Gleichheitsvergleich (prüfen Sie, ob sowohl Typen als auch Werte übereinstimmen) zwischen arg.getValue1 (), dem Typ 'long in der Java-Welt, und 0, dem Typ Number` in der JS-Welt. )tun.

Für Nashorn bis Java8u91 gibt dieser Ausdruck true zurück, wenn arg.getValue1 () == 0L auf der Java-Seite true ist. Seit Java8u92 ist das Ergebnis jedoch false. Der Grund ist, dass die Werte auf der linken und rechten Seite von ** === 0 sind, aber die Typen unterschiedlich sind **.

Verhalten unter Java 8u91

//Arg auf der Java-Seite.getValue1()Wenn ist 0L
arg.getValue1() === 0 // arg.getValue1()Ist Nummer(0)Und das Ergebnis ist wahr

Verhalten unter Java 8u92

//Arg auf der Java-Seite.getValue1()Wenn ist 0L
arg.getValue1() === 0 // arg.getValue1()Ist Objekt(0)Und das Ergebnis ist falsch

Mit anderen Worten, selbst wenn sich der Typ des von der Java-Seite übergebenen Werts nicht mit "long" geändert hat, wird der von Nashorn zugeordnete Typ auf der JS-Seite "Number" sein, wenn die Java-Version von "8u91" oder weniger auf "8u92" oder mehr steigt. Es ändert sich von "zu" Objekt ". Infolgedessen ändert sich im obigen Code das Ergebnis, das durch die exakte Gleichheitsoperation "===" zurückgegeben wird, die Nullteilung, die vermieden werden sollte, ist unvermeidbar, und "PositiveInfinity" wird mit dem Wert in der Mitte der Berechnung gemischt. Danach wurden alle Berechnungen zu unbeabsichtigten Werten, die Ergebnisse wurden an die Java-Seite zurückgegeben und es trat ein Fehler auf.

Lektion

Ich fand es wichtig, bereit zu sein, zu zweifeln, ohne es vom Kopf aus zu glauben, nur weil es offiziell war.

Recommended Posts

Das Verhalten von JS auf Java SE 8 Nashorn änderte sich plötzlich
[Java] [Spring] Testen Sie das Verhalten des Loggers
Überprüfen Sie das Verhalten von Java Intrinsic Locks mit bpftrace
Ich habe das Verhalten von Java Scanner und .nextLine () nicht wirklich verstanden.
20190803_Java & k8s on Azure Die Geschichte vom Festivalbesuch
Java-Anfänger fassten das Verhalten von Array und ArrayList kurz zusammen
Lassen Sie uns über die vorübergehende Erfahrung von Java SE 8 Programmer II sprechen
Die Geschichte, das Verhalten von String durch Passieren von Java nicht zu kennen
Untersuchungsmethode, wenn die CPU des Servers, auf dem Java ausgeführt wird, schwer ist
Über das Verhalten von Ruby Hash # ==
[Java Edition] Geschichte der Serialisierung
Hinweis zum Pfad von request.getRequestDispatcher
Die Geschichte von Java Gold SE8
Der Ursprung von Java-Lambda-Ausdrücken
Was war keine faire Verwendung der Java-API-Umleitung unter Android?
So ermitteln Sie den absoluten Pfad eines in Java ausgeführten Verzeichnisses