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 514f35a9b8
commit bad40ff239
3 changed files with 71 additions and 19 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();
}
/*

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}.
*/
@@ -116,7 +120,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));
@@ -141,8 +150,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)));
}
/**
@@ -310,4 +323,27 @@ class ProxyProjectionFactory implements ProjectionFactory, BeanClassLoaderAware
return true;
}
}
/**
* Holder for {@link ProjectionInformation} and whether the target projection type uses {@code default} interface
* methods.
*
* @since 2.7.13
*/
static class ProjectionMetadata {
final boolean hasDefaultMethods;
final ProjectionInformation projectionInformation;
ProjectionMetadata(boolean hasDefaultMethods, ProjectionInformation projectionInformation) {
this.hasDefaultMethods = hasDefaultMethods;
this.projectionInformation = projectionInformation;
}
public static ProjectionMetadata create(Class<?> projectionType, ProjectionInformation projectionInformation) {
return new ProjectionMetadata(DefaultMethodInvokingMethodInterceptor.hasDefaultMethods(projectionType),
projectionInformation);
}
}
}

View File

@@ -139,16 +139,31 @@ class ProxyProjectionFactoryUnitTests {
assertThat(result).hasSize(6);
}
@Test // DATACMNS-655
@Test // DATACMNS-655, GH-2831
void invokesDefaultMethodOnProxy() {
CustomerExcerptWithDefaultMethod excerpt = factory.createProjection(CustomerExcerptWithDefaultMethod.class);
Advised advised = (Advised) ReflectionTestUtils.getField(Proxy.getInvocationHandler(excerpt), "advised");
Advisor[] advisors = advised.getAdvisors();
assertThat(advisors.length).isGreaterThan(0);
assertThat(advisors[0].getAdvice()).isInstanceOf(DefaultMethodInvokingMethodInterceptor.class);
}
@Test // GH-2831
void doesNotRegisterDefaultMethodInvokingMethodInterceptor() {
CustomerExcerpt excerpt = factory.createProjection(CustomerExcerpt.class);
Advised advised = (Advised) ReflectionTestUtils.getField(Proxy.getInvocationHandler(excerpt), "advised");
Advisor[] advisors = advised.getAdvisors();
assertThat(advisors.length).isGreaterThan(0);
assertThat(advisors[0].getAdvice()).isInstanceOf(DefaultMethodInvokingMethodInterceptor.class);
for (Advisor advisor : advisors) {
assertThat(advisor).isNotInstanceOf(DefaultMethodInvokingMethodInterceptor.class);
}
}
@Test // DATACMNS-648
@@ -273,8 +288,7 @@ class ProxyProjectionFactoryUnitTests {
customer.address.city = "New York";
customer.address.zipCode = "ZIP";
CustomerWithOptionalHavingProjection excerpt = factory.createProjection(CustomerWithOptionalHavingProjection.class,
customer);
CustomerWithOptionalHavingProjection excerpt = factory.createProjection(CustomerWithOptionalHavingProjection.class, customer);
assertThat(excerpt.getFirstname()).isEqualTo("Dave");
assertThat(excerpt.getAddress()).hasValueSatisfying(addressExcerpt -> {
@@ -345,6 +359,13 @@ class ProxyProjectionFactoryUnitTests {
Map<String, Object> getData();
}
interface CustomerExcerptWithDefaultMethod extends CustomerExcerpt {
default String getFirstnameAndId() {
return getFirstname() + " " + getId();
}
}
interface AddressExcerpt {
String getZipCode();