Hallo an alle. Heute möchten wir Ihnen eine Übersetzung eines Artikels mitteilen, der speziell für Studenten des 
Java Developer- Kurses erstellt wurde.
In meinem Artikel über Spezifikationsmuster habe ich die zugrunde liegende Komponente, die bei der Implementierung sehr hilfreich war, nicht ausdrücklich erwähnt. Hier werde ich mehr über die 
JavaBeanUtil- Klasse sprechen, mit der ich den Wert eines Objektfelds abgerufen habe. In diesem Beispiel war es 
FxTransaction .

Natürlich werden Sie sagen, dass Sie 
Apache Commons BeanUtils oder eine seiner Alternativen verwenden können, um das gleiche Ergebnis zu 
erzielen . Aber ich war daran interessiert, mich damit zu beschäftigen, und was ich gelernt habe, funktioniert viel schneller als jede Bibliothek, die auf der Basis der bekannten 
Java Reflection erstellt wurde . 
Eine Technologie, die eine sehr langsame Reflexion vermeidet, ist der 
invokedynamic Bytecode- 
invokedynamic . Kurz gesagt, die Manifestation von 
invokedynamic (oder „indy“) war die schwerwiegendste Neuerung in Java 7, die den Weg für die Implementierung dynamischer Sprachen über die JVM mithilfe dynamischer Methodenaufrufe ebnete. Später in Java 8 ermöglichte dies auch 
Lambda-Ausdrücke und Methodenreferenzen sowie eine verbesserte Verkettung von Zeichenfolgen in Java 9.
Kurz gesagt, die Technik, die ich unten beschreiben werde, verwendet 
LambdaMetafactory und 
MethodHandle, um dynamisch eine Implementierung der 
Funktionsschnittstelle zu erstellen. Die Funktion ist die 
einzige Methode , die einen Aufruf an die eigentliche Zielmethode delegiert, wobei der Code im Lambda definiert ist.
In diesem Fall ist die Zielmethode ein Getter, der direkten Zugriff auf das Feld hat, das wir lesen möchten. Außerdem muss ich sagen, dass Sie die folgenden Code-Schnipsel recht einfach finden, wenn Sie mit den Innovationen in Java 8 vertraut sind. Andernfalls kann der Code auf den ersten Blick kompliziert erscheinen.
Schauen Sie sich das provisorische JavaBeanUtil an
Die folgende 
getFieldValue Methode ist eine Dienstprogrammmethode zum Lesen von Werten aus einem JavaBean-Feld. Es benötigt ein JavaBean-Objekt und einen Feldnamen. Der Feldname kann einfach (z. B. 
fieldA ) oder verschachtelt und durch 
fieldA getrennt sein (z. B. 
nestedJavaBean.nestestJavaBean.fieldA ).
 private static final Pattern FIELD_SEPARATOR = Pattern.compile("\\."); private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); private static final ClassValue<Map<String, Function>> CACHE = new ClassValue<Map<String, Function>>() { @Override protected Map<String, Function> computeValue(Class<?> type) { return new ConcurrentHashMap<>(); } }; public static <T> T getFieldValue(Object javaBean, String fieldName) { return (T) getCachedFunction(javaBean.getClass(), fieldName).apply(javaBean); } private static Function getCachedFunction(Class<?> javaBeanClass, String fieldName) { final Function function = CACHE.get(javaBeanClass).get(fieldName); if (function != null) { return function; } return createAndCacheFunction(javaBeanClass, fieldName); } private static Function createAndCacheFunction(Class<?> javaBeanClass, String path) { return cacheAndGetFunction(path, javaBeanClass, createFunctions(javaBeanClass, path) .stream() .reduce(Function::andThen) .orElseThrow(IllegalStateException::new) ); } private static Function cacheAndGetFunction(String path, Class<?> javaBeanClass, Function functionToBeCached) { Function cachedFunction = CACHE.get(javaBeanClass).putIfAbsent(path, functionToBeCached); return cachedFunction != null ? cachedFunction : functionToBeCached; } 
Um die Leistung zu verbessern, 
fieldName ich eine dynamisch erstellte Funktion zwischen, die tatsächlich einen Wert aus einem Feld mit dem Namen 
fieldName . Wie Sie sehen, gibt es in der Methode 
getCachedFunction einen "schnellen" Pfad, der 
ClassValue zum Zwischenspeichern verwendet, und einen "langsamen" Pfad " 
createAndCacheFunction ", der ausgeführt wird, wenn kein Wert im Cache gefunden wird.
Die Methode 
createFunctions ruft eine Methode auf, die eine Liste von Funktionen zurückgibt, die mit 
Function::andThen verkettet werden. Das Verknüpfen von Funktionen in einer Kette kann als verschachtelte Aufrufe dargestellt werden, ähnlich wie 
getNestedJavaBean().getNestJavaBean().getNestJavaBean().getFieldA() . Danach 
cacheAndGetFunction wir die Funktion einfach in den Cache ein, indem 
cacheAndGetFunction Methode 
cacheAndGetFunction aufrufen.
Wenn Sie sich die Erstellung der Funktion genau ansehen, müssen wir die Felder im 
path wie folgt durchgehen:
 private static List<Function> createFunctions(Class<?> javaBeanClass, String path) { List<Function> functions = new ArrayList<>(); Stream.of(FIELD_SEPARATOR.split(path)) .reduce(javaBeanClass, (nestedJavaBeanClass, fieldName) -> { Tuple2<? extends Class, Function> getFunction = createFunction(fieldName, nestedJavaBeanClass); functions.add(getFunction._2); return getFunction._1; }, (previousClass, nextClass) -> nextClass); return functions; } private static Tuple2<? extends Class, Function> createFunction(String fieldName, Class<?> javaBeanClass) { return Stream.of(javaBeanClass.getDeclaredMethods()) .filter(JavaBeanUtil::isGetterMethod) .filter(method -> StringUtils.endsWithIgnoreCase(method.getName(), fieldName)) .map(JavaBeanUtil::createTupleWithReturnTypeAndGetter) .findFirst() .orElseThrow(IllegalStateException::new); } 
Die obige Methode 
createFunctions für jedes Feld 
fieldName und die Klasse, in der es deklariert ist, ruft die Methode 
createFunction , die mit 
javaBeanClass.getDeclaredMethods() nach dem gewünschten Getter 
javaBeanClass.getDeclaredMethods() . Sobald der Getter gefunden wurde, wird er in ein Tupel-Tupel (Tupel aus der 
Vavr- Bibliothek) 
konvertiert , das den vom Getter zurückgegebenen Typ und eine dynamisch erstellte Funktion enthält, die sich so verhält, als wäre sie selbst ein Getter.
Ein Tupel wird mit der Methode 
createTupleWithReturnTypeAndGetter in Kombination mit der Methode 
createCallSite wie folgt erstellt:
 private static Tuple2<? extends Class, Function> createTupleWithReturnTypeAndGetter(Method getterMethod) { try { return Tuple.of( getterMethod.getReturnType(), (Function) createCallSite(LOOKUP.unreflect(getterMethod)).getTarget().invokeExact() ); } catch (Throwable e) { throw new IllegalArgumentException("Lambda creation failed for getterMethod (" + getterMethod.getName() + ").", e); } } private static CallSite createCallSite(MethodHandle getterMethodHandle) throws LambdaConversionException { return LambdaMetafactory.metafactory(LOOKUP, "apply", MethodType.methodType(Function.class), MethodType.methodType(Object.class, Object.class), getterMethodHandle, getterMethodHandle.type()); } 
In den beiden oben genannten Methoden verwende ich eine Konstante namens 
LOOKUP , die nur auf 
MethodHandles.Lookup verweist . Damit kann ich eine 
direkte Verknüpfung zu einer Methode (direktes Methodenhandle) erstellen, die auf einem zuvor gefundenen Getter basiert. Schließlich wird das erstellte 
MethodHandle an die Methode 
createCallSite , in der der Lambda-Body für die Funktion mit 
LambdaMetafactory erstellt wird . Von dort können wir letztendlich eine Instanz von 
CallSite erhalten , die der „Verwalter“ der Funktion ist.
Beachten Sie, dass Sie für Setter einen ähnlichen Ansatz verwenden können, indem Sie 
BiFunction anstelle von 
Function verwenden .
Benchmark
Um die Leistung zu messen, habe ich das wunderbare JMH-Tool ( 
Java Microbenchmark Harness ) verwendet, das wahrscheinlich Teil von JDK 12 ist ( 
Anmerkung des 
Übersetzers: Ja, jmh ist in Java 9 enthalten ). Wie Sie wahrscheinlich wissen, ist das Ergebnis plattformabhängig. Als Referenz: Ich verwende 
1x6 i5-8600K 3,6 Linux x86_64, Oracle JDK 8u191 GraalVM EE 1.0.0-rc9 .
Zum Vergleich habe ich die 
Apache Commons BeanUtils-Bibliothek ausgewählt , die den meisten Java-Entwicklern weithin bekannt ist, und eine ihrer Alternativen namens 
Jodd BeanUtil , die angeblich 
fast 20% schneller ist .
Der Benchmark-Code lautet wie folgt:
 @Fork(3) @Warmup(iterations = 5, time = 3) @Measurement(iterations = 5, time = 1) @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @State(Scope.Thread) public class JavaBeanUtilBenchmark { @Param({ "fieldA", "nestedJavaBean.fieldA", "nestedJavaBean.nestedJavaBean.fieldA", "nestedJavaBean.nestedJavaBean.nestedJavaBean.fieldA" }) String fieldName; JavaBean javaBean; @Setup public void setup() { NestedJavaBean nestedJavaBean3 = NestedJavaBean.builder().fieldA("nested-3").build(); NestedJavaBean nestedJavaBean2 = NestedJavaBean.builder().fieldA("nested-2").nestedJavaBean(nestedJavaBean3).build(); NestedJavaBean nestedJavaBean1 = NestedJavaBean.builder().fieldA("nested-1").nestedJavaBean(nestedJavaBean2).build(); javaBean = JavaBean.builder().fieldA("fieldA").nestedJavaBean(nestedJavaBean1).build(); } @Benchmark public Object invokeDynamic() { return JavaBeanUtil.getFieldValue(javaBean, fieldName); }  @Benchmark public Object apacheBeanUtils() throws Exception { return PropertyUtils.getNestedProperty(javaBean, fieldName); }  @Benchmark public Object joddBean() { return BeanUtil.declared.getProperty(javaBean, fieldName); } public static void main(String... args) throws IOException, RunnerException { Main.main(args); } } 
Der Benchmark definiert vier Szenarien für verschiedene Verschachtelungsebenen des Feldes. Für jedes Feld führt JMH 5 Iterationen von 3 Sekunden zum Aufwärmen und dann 5 Iterationen von 1 Sekunde für die eigentliche Messung durch. Jedes Szenario wird dreimal wiederholt, um bessere Messungen zu erhalten.
Ergebnisse
Beginnen wir mit den für das JDK 
8u191 kompilierten Ergebnissen:
 Oracle JDK 8u191
Oracle JDK 8u191Das Worst-Case-Szenario mit dem 
invokedynamic Ansatz ist viel schneller als das schnellste der beiden anderen Bibliotheken. Dies ist ein großer Unterschied, und wenn Sie an den Ergebnissen zweifeln, können Sie den 
Quellcode jederzeit herunterladen und damit spielen, wie Sie möchten.
Nun wollen wir sehen, wie der gleiche Test mit 
GraalVM EE 1.0.0-rc9. GraalVM EE 1.0.0-rc9
GraalVM EE 1.0.0-rc9Die vollständigen Ergebnisse können 
hier mit dem wunderschönen JMH Visualizer angezeigt werden.
Beobachtungen
Ein so großer Unterschied war auf die Tatsache zurückzuführen, dass der JIT-Compiler 
CallSite und 
MethodHandle gut kennt und sie im Gegensatz zum Reflection-Ansatz 
MethodHandle kann. Außerdem können Sie sehen, wie vielversprechend 
GraalVM ist . Sein Compiler leistet wirklich großartige Arbeit, die die Reflexionsleistung erheblich verbessern kann.
Wenn Sie neugierig sind und tiefer eintauchen möchten, empfehlen wir Ihnen, den Code aus meinem 
Github- Repository zu holen. Denken Sie daran, ich rate Ihnen nicht, ein selbst erstelltes 
JavaBeanUtil zu 
JavaBeanUtil , um es in der Produktion zu verwenden. Mein Ziel ist es einfach, mein Experiment und die Möglichkeiten zu zeigen, die wir durch 
invokedynamic .
Die Übersetzung ist beendet und wir laden alle zu einem 
kostenlosen Webinar am 13. Juni ein, in dem wir untersuchen, wie der Docker für einen Java-Entwickler nützlich sein kann: wie man ein Docker-Image mit einer Java-Anwendung erstellt und wie man damit interagiert.