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