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.
This commit is contained in:
@@ -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<MethodHandles.Lookup> constructor;
|
||||
private final MethodHandleLookup methodHandleLookup = MethodHandleLookup.getMethodHandleLookup();
|
||||
private final Map<Method, MethodHandle> methodHandleCache = new ConcurrentReferenceHashMap<Method, MethodHandle>(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<Lookup> 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<Lookup> getLookupConstructor() {
|
||||
|
||||
try {
|
||||
|
||||
Constructor<Lookup> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user