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:
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user