DATACMNS-991 - Polishing.

Refactor conditional lookup strategies into enum instances reflecting the open/encapsulated lookup strategy.

Original pull request: #223.
This commit is contained in:
Mark Paluch
2017-05-31 15:54:22 +02:00
parent 6a39937d51
commit 2e532b3d21

View File

@@ -21,50 +21,25 @@ import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Optional;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.ProxyMethodInvocation;
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();
/**
* Creates a new {@link DefaultMethodInvokingMethodInterceptor}.
*/
public DefaultMethodInvokingMethodInterceptor() {
constructor = tryToGetLookupConstructor();
}
private static Constructor<Lookup> tryToGetLookupConstructor() {
try {
Constructor<Lookup> accessibleConstructor = Lookup.class.getDeclaredConstructor(Class.class);
if (!accessibleConstructor.isAccessible()) {
accessibleConstructor.setAccessible(true);
}
return accessibleConstructor;
} catch (Exception ex) {
// this is the signal that we are on Java 9 and can't use the accessible constructor approach.
if (ex.getClass().getName().equals("java.lang.reflect.InaccessibleObjectException")) {
return null;
} else {
throw new IllegalStateException(ex);
}
}
}
/*
/*
* (non-Javadoc)
* @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
*/
@@ -78,27 +53,125 @@ public class DefaultMethodInvokingMethodInterceptor implements MethodInterceptor
}
Object[] arguments = invocation.getArguments();
Class<?> declaringClass = method.getDeclaringClass();
Object proxy = ((ProxyMethodInvocation) invocation).getProxy();
return getMethodHandle(method, arguments, declaringClass, proxy).bindTo(proxy).invokeWithArguments(arguments);
return methodHandleLookup.lookup(method).bindTo(proxy).invokeWithArguments(arguments);
}
private MethodHandle getMethodHandle(Method method, Object[] arguments, Class<?> declaringClass, Object proxy)
throws Throwable {
/**
* Strategies for {@link MethodHandle} lookup.
*
* @since 2.0
*/
enum MethodHandleLookup {
if (constructor != null) {
// java 8 variant
return constructor.newInstance(declaringClass).unreflectSpecial(method, declaringClass);
/**
* 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 Optional<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 {
Constructor<Lookup> constructor = this.constructor
.orElseThrow(() -> 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.isPresent();
}
},
/**
* 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!");
}
// java 9 variant
return MethodHandles.lookup() //
.findSpecial( //
method.getDeclaringClass(), //
method.getName(), //
MethodType.methodType(method.getReturnType(), method.getParameterTypes()), //
method.getDeclaringClass() //
);
private static Optional<Constructor<Lookup>> getLookupConstructor() {
try {
Constructor<Lookup> constructor = Lookup.class.getDeclaredConstructor(Class.class);
ReflectionUtils.makeAccessible(constructor);
return Optional.of(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 Optional.empty();
}
throw new IllegalStateException(ex);
}
}
}
}