invokedynamic
bytecode invokedynamic
. In short, invokedynamic
(or "indy") was the most serious innovation in Java 7, which allowed us to pave the way for implementing dynamic languages ​​on top of the JVM using dynamic method invocation. Later, in Java 8, it also allowed for lambda expressions and method reference references, as well as improved string concatenation in Java 9.getFieldValue
method getFieldValue
is a utility method used to read values ​​from a JavaBean field. It accepts a JavaBean object and a field name. The field name can be simple (for example, fieldA
) or nested, separated by dots (for example, 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; }
fieldName
. In the getCachedFunction
method, as you can see, there is a “fast” path using ClassValue for caching, and a “slow” path createAndCacheFunction
, which is executed if the value in the cache is not found.createFunctions
method calls a method that returns a list of functions that will be chained using Function::andThen
. Linking functions to each other in a chain can be represented as nested calls, similar to getNestedJavaBean().getNestJavaBean().getNestJavaBean().getFieldA()
. After that, we simply put the function in the cache by calling the cacheAndGetFunction
method.path
as follows: 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); }
createFunctions
method for each fieldName
field and the class in which it is declared calls the createFunction
method, which searches for the desired getter, using javaBeanClass.getDeclaredMethods()
. Once the getter is found, it is converted to a Tuple tuple (Tuple from the Vavr library), which contains the type returned by the getter, and a dynamically created function that will behave as if it were itself a getter.createTupleWithReturnTypeAndGetter
method in combination with the createCallSite
method as follows: 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()); }
LOOKUP
, which is simply a reference to MethodHandles.Lookup . With its help I can create a direct link to the method (direct method handle), based on the previously found getter. And finally, the created MethodHandle is passed to the createCallSite
method, in which the body of the lambda is created for the function using the LambdaMetafactory . From there, ultimately, we can get an instance of CallSite , which is the “keeper” of the function.1x6 i5-8600K 3,6 Linux x86_64, Oracle JDK 8u191 GraalVM EE 1.0.0-rc9
. @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); } /** * Reference: http://commons.apache.org/proper/commons-beanutils/ */ @Benchmark public Object apacheBeanUtils() throws Exception { return PropertyUtils.getNestedProperty(javaBean, fieldName); } /** * Reference: https://jodd.org/beanutil/ */ @Benchmark public Object joddBean() { return BeanUtil.declared.getProperty(javaBean, fieldName); } public static void main(String... args) throws IOException, RunnerException { Main.main(args); } }
8u191
:invokedynamic
approach is much faster than the fastest of the other two libraries. This is a huge difference, and if you doubt the results, you can always download the source code and play with it as you like.GraalVM EE 1.0.0-rc9.
CallSite
and MethodHandle
well and can inline them (inline), unlike the approach with reflection. In addition, you can see how promising GraalVM is . His compiler does a really awesome job that can significantly increase the performance of reflection.JavaBeanUtil
to use in production. My goal is simply to show my experiment and the possibilities that we can get from invokedynamic
.Source: https://habr.com/ru/post/455122/
All Articles