diff --git a/src/main/java/org/springframework/data/projection/DefaultMethodInvokingMethodInterceptor.java b/src/main/java/org/springframework/data/projection/DefaultMethodInvokingMethodInterceptor.java index 79cbdb373..dc18b6848 100644 --- a/src/main/java/org/springframework/data/projection/DefaultMethodInvokingMethodInterceptor.java +++ b/src/main/java/org/springframework/data/projection/DefaultMethodInvokingMethodInterceptor.java @@ -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 constructor; + private final MethodHandleLookup methodHandleLookup = MethodHandleLookup.getMethodHandleLookup(); - /** - * Creates a new {@link DefaultMethodInvokingMethodInterceptor}. - */ - public DefaultMethodInvokingMethodInterceptor() { - constructor = tryToGetLookupConstructor(); - } - - private static Constructor tryToGetLookupConstructor() { - - try { - - Constructor 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 = getLookupConstructor(); + + /* + * (non-Javadoc) + * @see org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.MethodHandleLookup#lookup(java.lang.reflect.Method) + */ + @Override + MethodHandle lookup(Method method) throws ReflectiveOperationException { + + Constructor 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> getLookupConstructor() { + + try { + + Constructor 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); + } + } } }