From 15597d35dcd98d651f6940feea8b4b00170e1dff Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Sat, 5 Jul 2014 16:55:56 +0200 Subject: [PATCH] DATACMNS-532 - Added support for default methods on repository interfaces. DefaultRepositoryInformation now excludes default methods from the query methods it detects. RepositoryFactorySupport adds a special MethodInterceptor to the proxy which handles the invocation of default methods. Related tickets: DATACMNS-535. --- .../support/DefaultRepositoryInformation.java | 3 +- .../support/RepositoryFactorySupport.java | 56 +++++++++++++++++++ .../data/util/ReflectionUtils.java | 14 +++++ 3 files changed, 72 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryInformation.java b/src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryInformation.java index 899a20e61..c06fa6561 100644 --- a/src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryInformation.java +++ b/src/main/java/org/springframework/data/repository/core/support/DefaultRepositoryInformation.java @@ -35,6 +35,7 @@ import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.CrudMethods; import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.util.ReflectionUtils; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -172,7 +173,7 @@ class DefaultRepositoryInformation implements RepositoryInformation { * @return */ private boolean isQueryMethodCandidate(Method method) { - return !method.isBridge() // + return !method.isBridge() && !ReflectionUtils.isDefaultMethod(method) // && (isQueryAnnotationPresentOn(method) || !isCustomMethod(method) && !isBaseClassMethod(method)); } diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java index 157da489b..02cd13b43 100644 --- a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java +++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java @@ -18,6 +18,8 @@ package org.springframework.data.repository.core.support; import static org.springframework.util.ReflectionUtils.*; import java.io.Serializable; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; @@ -27,6 +29,7 @@ import java.util.concurrent.ConcurrentHashMap; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; +import org.springframework.aop.ProxyMethodInvocation; import org.springframework.aop.framework.ProxyFactory; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.core.GenericTypeResolver; @@ -59,6 +62,8 @@ import org.springframework.util.ObjectUtils; public abstract class RepositoryFactorySupport implements BeanClassLoaderAware { private static final TypeDescriptor WRAPPER_TYPE = TypeDescriptor.valueOf(NullableWrapper.class); + private static final boolean IS_JAVA_8 = org.springframework.util.ClassUtils.isPresent("java.util.Optional", + RepositoryFactorySupport.class.getClassLoader()); private final Map REPOSITORY_INFORMATION_CACHE = new HashMap(); @@ -166,6 +171,10 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware { processor.postProcess(result, information); } + if (IS_JAVA_8) { + result.addAdvice(new DefaultMethodInvokingMethodInterceptor()); + } + result.addAdvice(new QueryExecutorMethodInterceptor(information, customImplementation, target)); return (T) result.getProxy(classLoader); @@ -437,6 +446,53 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware { } } + /** + * Method interceptor to invoke default methods on the repository proxy. + * + * @author Oliver Gierke + */ + private static class DefaultMethodInvokingMethodInterceptor implements MethodInterceptor { + + private final Constructor constructor; + + /** + * Creates a new {@link DefaultMethodInvokingMethodInterceptor}. + */ + public DefaultMethodInvokingMethodInterceptor() { + + try { + this.constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.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) + */ + @Override + public Object invoke(MethodInvocation invocation) throws Throwable { + + Method method = invocation.getMethod(); + + if (!org.springframework.data.util.ReflectionUtils.isDefaultMethod(method)) { + return invocation.proceed(); + } + + Object[] arguments = invocation.getArguments(); + Class declaringClass = method.getDeclaringClass(); + Object proxy = ((ProxyMethodInvocation) invocation).getProxy(); + + return constructor.newInstance(declaringClass, MethodHandles.Lookup.PRIVATE) + .unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(arguments); + } + } + /** * {@link QueryCreationListener} collecting the {@link QueryMethod}s created for all query methods of the repository * interface. diff --git a/src/main/java/org/springframework/data/util/ReflectionUtils.java b/src/main/java/org/springframework/data/util/ReflectionUtils.java index 9ebd35634..b1028e707 100644 --- a/src/main/java/org/springframework/data/util/ReflectionUtils.java +++ b/src/main/java/org/springframework/data/util/ReflectionUtils.java @@ -17,6 +17,8 @@ package org.springframework.data.util; import java.lang.annotation.Annotation; import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import org.springframework.beans.BeanUtils; import org.springframework.core.annotation.AnnotationUtils; @@ -51,6 +53,18 @@ public class ReflectionUtils { } } + /** + * Back-port of Java 8's {@code isDefault()} method on {@link Method}. + * + * @param method must not be {@literal null}. + * @return + */ + public static boolean isDefaultMethod(Method method) { + + return ((method.getModifiers() & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC) + && method.getDeclaringClass().isInterface(); + } + /** * A {@link FieldFilter} that has a description. *