From 6c7599c4899a355992beb7623fd10d755679b3d4 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Wed, 7 Jun 2017 10:37:17 +0200 Subject: [PATCH] DATACMNS-1074 - Backport of MethodHandle lookup caching. We now cache method handles after lookup on a best-effort basis to reuse them and prevent subsequent lookups as a MethodHandle lookup is expensive, in particular the lookup uses exceptions as control flow [0] during speculative lookup [1]. [0] http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/5b86f66575b7/src/share/classes/java/lang/invoke/MemberName.java#l978 [1] http://mail.openjdk.java.net/pipermail/core-libs-dev/2016-August/042770.html Original pull request: #221. --- ...efaultMethodInvokingMethodInterceptor.java | 171 +++++++++++++++--- 1 file changed, 148 insertions(+), 23 deletions(-) diff --git a/src/main/java/org/springframework/data/projection/DefaultMethodInvokingMethodInterceptor.java b/src/main/java/org/springframework/data/projection/DefaultMethodInvokingMethodInterceptor.java index 29801a3d3..8716f9aac 100644 --- a/src/main/java/org/springframework/data/projection/DefaultMethodInvokingMethodInterceptor.java +++ b/src/main/java/org/springframework/data/projection/DefaultMethodInvokingMethodInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,40 +15,35 @@ */ package org.springframework.data.projection; +import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.invoke.MethodType; import java.lang.reflect.Constructor; import java.lang.reflect.Method; +import java.util.Map; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.springframework.aop.ProxyMethodInvocation; +import org.springframework.util.ConcurrentReferenceHashMap; +import org.springframework.util.ConcurrentReferenceHashMap.ReferenceType; +import org.springframework.util.ReflectionUtils; /** * Method interceptor to invoke default methods on the repository proxy. * * @author Oliver Gierke + * @author Jens Schauder + * @author Mark Paluch */ public class DefaultMethodInvokingMethodInterceptor implements MethodInterceptor { - private final Constructor constructor; + private final MethodHandleLookup methodHandleLookup = MethodHandleLookup.getMethodHandleLookup(); + private final Map methodHandleCache = new ConcurrentReferenceHashMap(10, + ReferenceType.WEAK); - /** - * Creates a new {@link DefaultMethodInvokingMethodInterceptor}. - */ - public DefaultMethodInvokingMethodInterceptor() { - - try { - this.constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class); - - if (!constructor.isAccessible()) { - constructor.setAccessible(true); - } - } catch (Exception o_O) { - throw new IllegalStateException(o_O); - } - } - - /* + /* * (non-Javadoc) * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation) */ @@ -57,15 +52,145 @@ public class DefaultMethodInvokingMethodInterceptor implements MethodInterceptor Method method = invocation.getMethod(); - if (!org.springframework.data.util.ReflectionUtils.isDefaultMethod(method)) { + if (!method.isDefault()) { return invocation.proceed(); } Object[] arguments = invocation.getArguments(); - Class declaringClass = method.getDeclaringClass(); Object proxy = ((ProxyMethodInvocation) invocation).getProxy(); - return constructor.newInstance(declaringClass).unreflectSpecial(method, declaringClass).bindTo(proxy) - .invokeWithArguments(arguments); + return getMethodHandle(method).bindTo(proxy).invokeWithArguments(arguments); + } + + private MethodHandle getMethodHandle(Method method) throws Exception { + + MethodHandle handle = methodHandleCache.get(method); + + if (handle == null) { + + handle = methodHandleLookup.lookup(method); + methodHandleCache.put(method, handle); + } + + return handle; + } + + /** + * Strategies for {@link MethodHandle} lookup. + * + * @since 2.0 + */ + enum MethodHandleLookup { + + /** + * Open (via reflection construction of {@link MethodHandles.Lookup}) method handle lookup. Works with Java 8 and + * with Java 9 permitting illegal access. + */ + OPEN { + + private final Constructor constructor = getLookupConstructor(); + + /* + * (non-Javadoc) + * @see org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.MethodHandleLookup#lookup(java.lang.reflect.Method) + */ + @Override + MethodHandle lookup(Method method) throws ReflectiveOperationException { + + if (constructor == null) { + throw new IllegalStateException("Could not obtain MethodHandles.lookup constructor"); + } + + return constructor.newInstance(method.getDeclaringClass()).unreflectSpecial(method, method.getDeclaringClass()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.MethodHandleLookup#isAvailable() + */ + @Override + boolean isAvailable() { + return constructor != null; + } + }, + + /** + * Encapsulated {@link MethodHandle} lookup working on Java 9. + */ + ENCAPSULATED { + + /* + * (non-Javadoc) + * @see org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.MethodHandleLookup#lookup(java.lang.reflect.Method) + */ + @Override + MethodHandle lookup(Method method) throws ReflectiveOperationException { + + MethodType methodType = MethodType.methodType(method.getReturnType(), method.getParameterTypes()); + + return MethodHandles.lookup().findSpecial(method.getDeclaringClass(), method.getName(), methodType, + method.getDeclaringClass()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.MethodHandleLookup#isAvailable() + */ + @Override + boolean isAvailable() { + return true; + } + }; + + /** + * Lookup a {@link MethodHandle} given {@link Method} to look up. + * + * @param method must not be {@literal null}. + * @return the method handle. + * @throws ReflectiveOperationException + */ + abstract MethodHandle lookup(Method method) throws ReflectiveOperationException; + + /** + * @return {@literal true} if the lookup is available. + */ + abstract boolean isAvailable(); + + /** + * Obtain the first available {@link MethodHandleLookup}. + * + * @return the {@link MethodHandleLookup} + * @throws IllegalStateException if no {@link MethodHandleLookup} is available. + */ + public static MethodHandleLookup getMethodHandleLookup() { + + for (MethodHandleLookup lookup : MethodHandleLookup.values()) { + if (lookup.isAvailable()) { + return lookup; + } + } + + throw new IllegalStateException("No MethodHandleLookup available!"); + } + + private static Constructor getLookupConstructor() { + + try { + + Constructor constructor = Lookup.class.getDeclaredConstructor(Class.class); + ReflectionUtils.makeAccessible(constructor); + + return constructor; + + } catch (Exception ex) { + + // this is the signal that we are on Java 9 (encapsulated) and can't use the accessible constructor approach. + if (ex.getClass().getName().equals("java.lang.reflect.InaccessibleObjectException")) { + return null; + } + + throw new IllegalStateException(ex); + } + } } }