Browse Source

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
3.0.x
Mark Paluch 3 years ago
parent
commit
2b7fed55a0
No known key found for this signature in database
GPG Key ID: 4406B84C1661DCD1
  1. 13
      src/main/java/org/springframework/data/projection/DefaultMethodInvokingMethodInterceptor.java
  2. 50
      src/main/java/org/springframework/data/projection/ProxyProjectionFactory.java
  3. 31
      src/test/java/org/springframework/data/projection/ProxyProjectionFactoryUnitTests.java

13
src/main/java/org/springframework/data/projection/DefaultMethodInvokingMethodInterceptor.java

@ -23,10 +23,10 @@ import java.lang.reflect.Constructor; @@ -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 @@ -56,15 +56,10 @@ public class DefaultMethodInvokingMethodInterceptor implements MethodInterceptor
*/
public static boolean hasDefaultMethods(Class<?> interfaceClass) {
Method[] methods = ReflectionUtils.getAllDeclaredMethods(interfaceClass);
for (Method method : methods) {
if (method.isDefault()) {
return true;
}
}
AtomicBoolean atomicBoolean = new AtomicBoolean();
ReflectionUtils.doWithMethods(interfaceClass, method -> atomicBoolean.set(true), Method::isDefault);
return false;
return atomicBoolean.get();
}
@Nullable

50
src/main/java/org/springframework/data/projection/ProxyProjectionFactory.java

@ -29,6 +29,7 @@ import org.springframework.beans.factory.BeanClassLoaderAware; @@ -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; @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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.0.7
*/
record ProjectionMetadata(boolean hasDefaultMethods, ProjectionInformation projectionInformation) {
public static ProjectionMetadata create(Class<?> projectionType, ProjectionInformation projectionInformation) {
return new ProjectionMetadata(DefaultMethodInvokingMethodInterceptor.hasDefaultMethods(projectionType),
projectionInformation);
}
}
}

31
src/test/java/org/springframework/data/projection/ProxyProjectionFactoryUnitTests.java

@ -28,7 +28,7 @@ import java.util.Map; @@ -28,7 +28,7 @@ import java.util.Map;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.springframework.aop.Advisor;
import org.springframework.aop.TargetClassAware;
import org.springframework.aop.framework.Advised;
import org.springframework.test.util.ReflectionTestUtils;
@ -137,10 +137,10 @@ class ProxyProjectionFactoryUnitTests { @@ -137,10 +137,10 @@ class ProxyProjectionFactoryUnitTests {
assertThat(result).hasSize(6);
}
@Test // DATACMNS-655
@Test // DATACMNS-655, GH-2831
void invokesDefaultMethodOnProxy() {
var excerpt = factory.createProjection(CustomerExcerpt.class);
var excerpt = factory.createProjection(CustomerExcerptWithDefaultMethod.class);
var advised = (Advised) ReflectionTestUtils.getField(Proxy.getInvocationHandler(excerpt), "advised");
var advisors = advised.getAdvisors();
@ -149,6 +149,21 @@ class ProxyProjectionFactoryUnitTests { @@ -149,6 +149,21 @@ class ProxyProjectionFactoryUnitTests {
assertThat(advisors[0].getAdvice()).isInstanceOf(DefaultMethodInvokingMethodInterceptor.class);
}
@Test // GH-2831
void doesNotRegisterDefaultMethodInvokingMethodInterceptor() {
var excerpt = factory.createProjection(CustomerExcerpt.class);
var advised = (Advised) ReflectionTestUtils.getField(Proxy.getInvocationHandler(excerpt), "advised");
var advisors = advised.getAdvisors();
assertThat(advisors.length).isGreaterThan(0);
for (Advisor advisor : advisors) {
assertThat(advisor).isNotInstanceOf(DefaultMethodInvokingMethodInterceptor.class);
}
}
@Test // DATACMNS-648
void exposesProxyTarget() {
@ -271,8 +286,7 @@ class ProxyProjectionFactoryUnitTests { @@ -271,8 +286,7 @@ class ProxyProjectionFactoryUnitTests {
customer.address.city = "New York";
customer.address.zipCode = "ZIP";
var excerpt = factory.createProjection(CustomerWithOptionalHavingProjection.class,
customer);
var excerpt = factory.createProjection(CustomerWithOptionalHavingProjection.class, customer);
assertThat(excerpt.getFirstname()).isEqualTo("Dave");
assertThat(excerpt.getAddress()).hasValueSatisfying(addressExcerpt -> {
@ -343,6 +357,13 @@ class ProxyProjectionFactoryUnitTests { @@ -343,6 +357,13 @@ class ProxyProjectionFactoryUnitTests {
Map<String, Object> getData();
}
interface CustomerExcerptWithDefaultMethod extends CustomerExcerpt {
default String getFirstnameAndId() {
return getFirstname() + " " + getId();
}
}
interface AddressExcerpt {
String getZipCode();

Loading…
Cancel
Save