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.
This commit is contained in:
Oliver Gierke
2014-07-05 16:55:56 +02:00
parent 6f79d9d727
commit 15597d35dc
3 changed files with 72 additions and 1 deletions

View File

@@ -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));
}

View File

@@ -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<RepositoryInformationCacheKey, RepositoryInformation> REPOSITORY_INFORMATION_CACHE = new HashMap<RepositoryInformationCacheKey, RepositoryInformation>();
@@ -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<MethodHandles.Lookup> 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.

View File

@@ -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.
*