From 10f04f4afba0fcf8d759e47832b4280377976534 Mon Sep 17 00:00:00 2001 From: Patrick Strawderman Date: Sat, 12 Aug 2023 14:12:46 -0700 Subject: [PATCH] Generate lambdas to access default config methods For no-arg and single-arg default config methods, use LambdaMetafactory to generate Function and BiFunction accessors instead of going through the MethodHandle directly, which avoids some overhead. --- .../netflix/archaius/ConfigProxyFactory.java | 109 +++++++++++++++--- 1 file changed, 93 insertions(+), 16 deletions(-) diff --git a/archaius2-core/src/main/java/com/netflix/archaius/ConfigProxyFactory.java b/archaius2-core/src/main/java/com/netflix/archaius/ConfigProxyFactory.java index 9031e3f0..89c0f729 100644 --- a/archaius2-core/src/main/java/com/netflix/archaius/ConfigProxyFactory.java +++ b/archaius2-core/src/main/java/com/netflix/archaius/ConfigProxyFactory.java @@ -8,12 +8,16 @@ import com.netflix.archaius.api.annotations.Configuration; import com.netflix.archaius.api.annotations.DefaultValue; import com.netflix.archaius.api.annotations.PropertyName; +import com.netflix.archaius.util.Maps; import org.apache.commons.lang3.SystemUtils; import org.apache.commons.lang3.text.StrSubstitutor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; +import java.lang.invoke.CallSite; +import java.lang.invoke.LambdaConversionException; +import java.lang.invoke.LambdaMetafactory; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; @@ -29,6 +33,7 @@ import java.util.Map; import java.util.Set; import java.util.SortedSet; +import java.util.function.BiFunction; import java.util.function.Function; import java.util.stream.Collectors; @@ -191,7 +196,7 @@ T newProxy(final Class type, final String initialPrefix, boolean immutabl // prefix + lowerCamelCaseDerivedPropertyName final Map> invokers = new HashMap<>(); final Map propertyNames = new HashMap<>(); - + final InvocationHandler handler = (proxy, method, args) -> { MethodInvoker invoker = invokers.get(method); if (invoker != null) { @@ -244,7 +249,7 @@ else if ("toString".equals(method.getName())) { final Class returnType = m.getReturnType(); Function defaultSupplier = knownCollections.getOrDefault(returnType, (ignored) -> null); - + if (m.getAnnotation(DefaultValue.class) != null) { if (m.isDefault()) { throw new IllegalArgumentException("@DefaultValue cannot be defined on a method with a default implementation for method " @@ -308,36 +313,52 @@ private static Function memoize(T value) { private static Function createDefaultMethodSupplier(Method method, Class type, T proxyObject) { final MethodHandle methodHandle; + final MethodHandles.Lookup lookup; + try { if (SystemUtils.IS_JAVA_1_8) { Constructor constructor = MethodHandles.Lookup.class .getDeclaredConstructor(Class.class, int.class); constructor.setAccessible(true); - methodHandle = constructor.newInstance(type, MethodHandles.Lookup.PRIVATE) - .unreflectSpecial(method, type) - .bindTo(proxyObject); + lookup = constructor.newInstance(type, MethodHandles.Lookup.PRIVATE); + methodHandle = lookup.unreflectSpecial(method, type); } else { // Java 9 onwards - methodHandle = MethodHandles.lookup() + lookup = MethodHandles.lookup(); + methodHandle = lookup .findSpecial(type, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes()), - type) - .bindTo(proxyObject); + type); } - } catch (Throwable e) { + } catch (ReflectiveOperationException e) { throw new RuntimeException("Failed to create temporary object for " + type.getName(), e); } + if (methodHandle.type().parameterCount() == 1) { + Function getter = asFunction(lookup, methodHandle, method); + //noinspection unchecked + return (args) -> (T) getter.apply(proxyObject); + } else if (methodHandle.type().parameterCount() == 2) { + BiFunction getter = asBiFunction(lookup, methodHandle, method); + return (args) -> { + if (args == null) { + return null; + } + //noinspection unchecked + return (T) getter.apply(proxyObject, args[0]); + }; + } + + // Fall back to calling the MethodHandle directly + MethodHandle boundHandle = methodHandle.bindTo(proxyObject); + return (args) -> { try { - if (methodHandle.type().parameterCount() == 0) { - //noinspection unchecked - return (T) methodHandle.invokeWithArguments(); - } else if (args != null) { + if (args != null) { //noinspection unchecked - return (T) methodHandle.invokeWithArguments(args); + return (T) boundHandle.invokeWithArguments(args); } else { // This is a handle to a method WITH arguments, being called with none. This happens when toString() // is trying to build a representation of a proxy that has a parametrized property AND the interface @@ -345,6 +366,12 @@ private static Function createDefaultMethodSupplier(Method meth return null; } } catch (Throwable e) { + if (e instanceof RuntimeException) { + throw (RuntimeException) e; + } + if (e instanceof Error) { + throw (Error) e; + } throw new RuntimeException(e); } }; @@ -384,9 +411,9 @@ public T invoke(Object[] args) { // String getFooValue(String arg0, Integer arg1) // // called as getFooValue("bar", 1) would look for the property 'foo.1.bar' - Map values = new HashMap<>(); + Map values = Maps.newHashMap(args.length); for (int i = 0; i < args.length; i++) { - values.put("" + i, args[i]); + values.put(String.valueOf(i), args[i]); } String propName = new StrSubstitutor(values, "${", "}", '$').replace(nameAnnot); T result = getPropertyWithDefault(returnType, propName); @@ -401,4 +428,54 @@ R getPropertyWithDefault(Class type, String propName) { } }; } + + /** + * For a given no-args method or default method, build a Function instance that takes the instance and invokes + * the underlying method on it. + */ + @SuppressWarnings("unchecked") + private static Function asFunction(MethodHandles.Lookup lookup, MethodHandle methodHandle, Method method) { + try { + CallSite site = LambdaMetafactory.metafactory(lookup, + "apply", + MethodType.methodType(Function.class), + MethodType.methodType(Object.class, Object.class), + methodHandle, + MethodType.methodType(method.getReturnType(), method.getDeclaringClass())); + return (Function) site.getTarget().invokeExact(); + } catch (Throwable t) { + if (t instanceof RuntimeException) { + throw (RuntimeException) t; + } + if (t instanceof Error) { + throw (Error) t; + } + throw new RuntimeException(t); + } + } + + /** + * For a given single-arg method or default method, build a Function instance that takes the instance and invokes + * the underlying method on it. + */ + @SuppressWarnings("unchecked") + private static BiFunction asBiFunction(MethodHandles.Lookup lookup, MethodHandle methodHandle, Method method) { + try { + CallSite site = LambdaMetafactory.metafactory(lookup, + "apply", + MethodType.methodType(Function.class), + MethodType.methodType(Object.class, Object.class, Object.class), + methodHandle, + MethodType.methodType(method.getReturnType(), method.getDeclaringClass())); + return (BiFunction) site.getTarget().invokeExact(); + } catch (Throwable t) { + if (t instanceof RuntimeException) { + throw (RuntimeException) t; + } + if (t instanceof Error) { + throw (Error) t; + } + throw new RuntimeException(t); + } + } }