Skip to content

Commit bad40ff

Browse files
committed
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
1 parent 514f35a commit bad40ff

File tree

3 files changed

+71
-19
lines changed

3 files changed

+71
-19
lines changed

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

+4-9
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@
2323
import java.lang.reflect.Method;
2424
import java.lang.reflect.Modifier;
2525
import java.util.Map;
26+
import java.util.concurrent.atomic.AtomicBoolean;
2627

2728
import org.aopalliance.intercept.MethodInterceptor;
2829
import org.aopalliance.intercept.MethodInvocation;
29-
3030
import org.springframework.aop.ProxyMethodInvocation;
3131
import org.springframework.data.util.Lazy;
3232
import org.springframework.lang.Nullable;
@@ -56,15 +56,10 @@ public class DefaultMethodInvokingMethodInterceptor implements MethodInterceptor
5656
*/
5757
public static boolean hasDefaultMethods(Class<?> interfaceClass) {
5858

59-
Method[] methods = ReflectionUtils.getAllDeclaredMethods(interfaceClass);
60-
61-
for (Method method : methods) {
62-
if (method.isDefault()) {
63-
return true;
64-
}
65-
}
59+
AtomicBoolean atomicBoolean = new AtomicBoolean();
60+
ReflectionUtils.doWithMethods(interfaceClass, method -> atomicBoolean.set(true), Method::isDefault);
6661

67-
return false;
62+
return atomicBoolean.get();
6863
}
6964

7065
/*

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

+42-6
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.springframework.core.convert.support.DefaultConversionService;
3030
import org.springframework.core.convert.support.GenericConversionService;
3131
import org.springframework.data.convert.Jsr310Converters;
32+
import org.springframework.data.util.Lazy;
3233
import org.springframework.data.util.NullableWrapperConverters;
3334
import org.springframework.lang.Nullable;
3435
import org.springframework.util.Assert;
@@ -37,9 +38,9 @@
3738

3839
/**
3940
* A {@link ProjectionFactory} to create JDK proxies to back interfaces and handle method invocations on them. By
40-
* default accessor methods are supported. In case the delegating lookups result in an object of different type that the
41-
* projection interface method's return type, another projection will be created to transparently mitigate between the
42-
* types.
41+
* default, accessor methods are supported. In case the delegating lookups result in an object of different type that
42+
* the projection interface method's return type, another projection will be created to transparently mitigate between
43+
* the types.
4344
*
4445
* @author Oliver Gierke
4546
* @author Christoph Strobl
@@ -59,9 +60,12 @@ class ProxyProjectionFactory implements ProjectionFactory, BeanClassLoaderAware
5960
}
6061

6162
private final List<MethodInterceptorFactory> factories;
62-
private final Map<Class<?>, ProjectionInformation> projectionInformationCache = new ConcurrentReferenceHashMap<>();
63+
private final Map<Class<?>, ProjectionMetadata> projectionInformationCache = new ConcurrentReferenceHashMap<>();
6364
private @Nullable ClassLoader classLoader;
6465

66+
private final Lazy<DefaultMethodInvokingMethodInterceptor> defaultMethodInvokingMethodInterceptor = Lazy
67+
.of(DefaultMethodInvokingMethodInterceptor::new);
68+
6569
/**
6670
* Creates a new {@link ProxyProjectionFactory}.
6771
*/
@@ -116,7 +120,12 @@ public <T> T createProjection(Class<T> projectionType, Object source) {
116120
factory.setOpaque(true);
117121
factory.setInterfaces(projectionType, TargetAware.class);
118122

119-
factory.addAdvice(new DefaultMethodInvokingMethodInterceptor());
123+
ProjectionMetadata projectionMetadata = getProjectionMetadata(projectionType);
124+
125+
if (projectionMetadata.hasDefaultMethods) {
126+
factory.addAdvice(defaultMethodInvokingMethodInterceptor.get());
127+
}
128+
120129
factory.addAdvice(new TargetAwareMethodInterceptor(source.getClass()));
121130
factory.addAdvice(getMethodInterceptor(source, projectionType));
122131

@@ -141,8 +150,12 @@ public <T> T createProjection(Class<T> projectionType) {
141150
*/
142151
@Override
143152
public final ProjectionInformation getProjectionInformation(Class<?> projectionType) {
153+
return getProjectionMetadata(projectionType).projectionInformation;
154+
}
144155

145-
return projectionInformationCache.computeIfAbsent(projectionType, this::createProjectionInformation);
156+
private ProjectionMetadata getProjectionMetadata(Class<?> projectionType) {
157+
return projectionInformationCache.computeIfAbsent(projectionType,
158+
it -> ProjectionMetadata.create(it, createProjectionInformation(it)));
146159
}
147160

148161
/**
@@ -310,4 +323,27 @@ public boolean supports(Object source, Class<?> targetType) {
310323
return true;
311324
}
312325
}
326+
327+
/**
328+
* Holder for {@link ProjectionInformation} and whether the target projection type uses {@code default} interface
329+
* methods.
330+
*
331+
* @since 2.7.13
332+
*/
333+
static class ProjectionMetadata {
334+
335+
final boolean hasDefaultMethods;
336+
337+
final ProjectionInformation projectionInformation;
338+
339+
ProjectionMetadata(boolean hasDefaultMethods, ProjectionInformation projectionInformation) {
340+
this.hasDefaultMethods = hasDefaultMethods;
341+
this.projectionInformation = projectionInformation;
342+
}
343+
344+
public static ProjectionMetadata create(Class<?> projectionType, ProjectionInformation projectionInformation) {
345+
return new ProjectionMetadata(DefaultMethodInvokingMethodInterceptor.hasDefaultMethods(projectionType),
346+
projectionInformation);
347+
}
348+
}
313349
}

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

+25-4
Original file line numberDiff line numberDiff line change
@@ -139,10 +139,10 @@ void returnsAllPropertiesAsInputProperties() {
139139
assertThat(result).hasSize(6);
140140
}
141141

142-
@Test // DATACMNS-655
142+
@Test // DATACMNS-655, GH-2831
143143
void invokesDefaultMethodOnProxy() {
144144

145-
CustomerExcerpt excerpt = factory.createProjection(CustomerExcerpt.class);
145+
CustomerExcerptWithDefaultMethod excerpt = factory.createProjection(CustomerExcerptWithDefaultMethod.class);
146146

147147
Advised advised = (Advised) ReflectionTestUtils.getField(Proxy.getInvocationHandler(excerpt), "advised");
148148
Advisor[] advisors = advised.getAdvisors();
@@ -151,6 +151,21 @@ void invokesDefaultMethodOnProxy() {
151151
assertThat(advisors[0].getAdvice()).isInstanceOf(DefaultMethodInvokingMethodInterceptor.class);
152152
}
153153

154+
@Test // GH-2831
155+
void doesNotRegisterDefaultMethodInvokingMethodInterceptor() {
156+
157+
CustomerExcerpt excerpt = factory.createProjection(CustomerExcerpt.class);
158+
159+
Advised advised = (Advised) ReflectionTestUtils.getField(Proxy.getInvocationHandler(excerpt), "advised");
160+
Advisor[] advisors = advised.getAdvisors();
161+
162+
assertThat(advisors.length).isGreaterThan(0);
163+
164+
for (Advisor advisor : advisors) {
165+
assertThat(advisor).isNotInstanceOf(DefaultMethodInvokingMethodInterceptor.class);
166+
}
167+
}
168+
154169
@Test // DATACMNS-648
155170
void exposesProxyTarget() {
156171

@@ -273,8 +288,7 @@ void supportsOptionalWithProjectionAsReturnTypeIfPresent() {
273288
customer.address.city = "New York";
274289
customer.address.zipCode = "ZIP";
275290

276-
CustomerWithOptionalHavingProjection excerpt = factory.createProjection(CustomerWithOptionalHavingProjection.class,
277-
customer);
291+
CustomerWithOptionalHavingProjection excerpt = factory.createProjection(CustomerWithOptionalHavingProjection.class, customer);
278292

279293
assertThat(excerpt.getFirstname()).isEqualTo("Dave");
280294
assertThat(excerpt.getAddress()).hasValueSatisfying(addressExcerpt -> {
@@ -345,6 +359,13 @@ interface CustomerExcerpt {
345359
Map<String, Object> getData();
346360
}
347361

362+
interface CustomerExcerptWithDefaultMethod extends CustomerExcerpt {
363+
364+
default String getFirstnameAndId() {
365+
return getFirstname() + " " + getId();
366+
}
367+
}
368+
348369
interface AddressExcerpt {
349370

350371
String getZipCode();

0 commit comments

Comments
 (0)