Refine allocation of DefaultMethodInvokingMethodInterceptor.

We now reduce allocations of DefaultMethodInvokingMethodInterceptor by reusing DefaultMethodInvokingMethodInterceptor within a ProxyProjectionFactory. We also reduced allocations during default method discovery and reuse the default methods information within the projection information cache.

Closes #2831
This commit is contained in:
Mark Paluch
2023-05-17 10:28:20 +02:00
parent 82241238ab
commit c6879a8bf6
3 changed files with 67 additions and 27 deletions

View File

@@ -23,10 +23,10 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.ProxyMethodInvocation;
import org.springframework.data.util.Lazy;
import org.springframework.lang.Nullable;
@@ -56,15 +56,10 @@ public class DefaultMethodInvokingMethodInterceptor implements MethodInterceptor
*/
public static boolean hasDefaultMethods(Class<?> interfaceClass) {
Method[] methods = ReflectionUtils.getAllDeclaredMethods(interfaceClass);
AtomicBoolean atomicBoolean = new AtomicBoolean();
ReflectionUtils.doWithMethods(interfaceClass, method -> atomicBoolean.set(true), Method::isDefault);
for (Method method : methods) {
if (method.isDefault()) {
return true;
}
}
return false;
return atomicBoolean.get();
}
@Nullable

View File

@@ -29,6 +29,7 @@ import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.convert.Jsr310Converters;
import org.springframework.data.util.Lazy;
import org.springframework.data.util.NullableWrapperConverters;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -37,9 +38,9 @@ import org.springframework.util.ConcurrentReferenceHashMap;
/**
* A {@link ProjectionFactory} to create JDK proxies to back interfaces and handle method invocations on them. By
* default accessor methods are supported. In case the delegating lookups result in an object of different type that the
* projection interface method's return type, another projection will be created to transparently mitigate between the
* types.
* default, accessor methods are supported. In case the delegating lookups result in an object of different type that
* the projection interface method's return type, another projection will be created to transparently mitigate between
* the types.
*
* @author Oliver Gierke
* @author Christoph Strobl
@@ -59,9 +60,12 @@ class ProxyProjectionFactory implements ProjectionFactory, BeanClassLoaderAware
}
private final List<MethodInterceptorFactory> factories;
private final Map<Class<?>, ProjectionInformation> projectionInformationCache = new ConcurrentReferenceHashMap<>();
private final Map<Class<?>, ProjectionMetadata> projectionInformationCache = new ConcurrentReferenceHashMap<>();
private @Nullable ClassLoader classLoader;
private final Lazy<DefaultMethodInvokingMethodInterceptor> defaultMethodInvokingMethodInterceptor = Lazy
.of(DefaultMethodInvokingMethodInterceptor::new);
/**
* Creates a new {@link ProxyProjectionFactory}.
*/
@@ -108,7 +112,12 @@ class ProxyProjectionFactory implements ProjectionFactory, BeanClassLoaderAware
factory.setOpaque(true);
factory.setInterfaces(projectionType, TargetAware.class);
factory.addAdvice(new DefaultMethodInvokingMethodInterceptor());
ProjectionMetadata projectionMetadata = getProjectionMetadata(projectionType);
if (projectionMetadata.hasDefaultMethods) {
factory.addAdvice(defaultMethodInvokingMethodInterceptor.get());
}
factory.addAdvice(new TargetAwareMethodInterceptor(source.getClass()));
factory.addAdvice(getMethodInterceptor(source, projectionType));
@@ -125,8 +134,12 @@ class ProxyProjectionFactory implements ProjectionFactory, BeanClassLoaderAware
@Override
public final ProjectionInformation getProjectionInformation(Class<?> projectionType) {
return getProjectionMetadata(projectionType).projectionInformation;
}
return projectionInformationCache.computeIfAbsent(projectionType, this::createProjectionInformation);
private ProjectionMetadata getProjectionMetadata(Class<?> projectionType) {
return projectionInformationCache.computeIfAbsent(projectionType,
it -> ProjectionMetadata.create(it, createProjectionInformation(it)));
}
/**
@@ -193,13 +206,11 @@ class ProxyProjectionFactory implements ProjectionFactory, BeanClassLoaderAware
*
* @author Oliver Gierke
*/
private static class TargetAwareMethodInterceptor implements MethodInterceptor {
private record TargetAwareMethodInterceptor(Class<?> targetType) implements MethodInterceptor {
private static final Method GET_TARGET_CLASS_METHOD;
private static final Method GET_TARGET_METHOD;
private final Class<?> targetType;
static {
try {
GET_TARGET_CLASS_METHOD = TargetAware.class.getMethod("getTargetClass");
@@ -214,10 +225,9 @@ class ProxyProjectionFactory implements ProjectionFactory, BeanClassLoaderAware
*
* @param targetType must not be {@literal null}.
*/
public TargetAwareMethodInterceptor(Class<?> targetType) {
private TargetAwareMethodInterceptor {
Assert.notNull(targetType, "Target type must not be null");
this.targetType = targetType;
}
@Nullable
@@ -239,7 +249,7 @@ class ProxyProjectionFactory implements ProjectionFactory, BeanClassLoaderAware
*
* @author Oliver Gierke
*/
private static enum MapAccessingMethodInterceptorFactory implements MethodInterceptorFactory {
private enum MapAccessingMethodInterceptorFactory implements MethodInterceptorFactory {
INSTANCE;
@@ -260,7 +270,7 @@ class ProxyProjectionFactory implements ProjectionFactory, BeanClassLoaderAware
*
* @author Oliver Gierke
*/
private static enum PropertyAccessingMethodInvokerFactory implements MethodInterceptorFactory {
private enum PropertyAccessingMethodInvokerFactory implements MethodInterceptorFactory {
INSTANCE;
@@ -274,4 +284,18 @@ class ProxyProjectionFactory implements ProjectionFactory, BeanClassLoaderAware
return true;
}
}
/**
* Holder for {@link ProjectionInformation} and whether the target projection type uses {@code default} interface
* methods.
*
* @since 3.1.1
*/
record ProjectionMetadata(boolean hasDefaultMethods, ProjectionInformation projectionInformation) {
public static ProjectionMetadata create(Class<?> projectionType, ProjectionInformation projectionInformation) {
return new ProjectionMetadata(DefaultMethodInvokingMethodInterceptor.hasDefaultMethods(projectionType),
projectionInformation);
}
}
}