Skip to content

Commit 54ffee0

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 e4f05de commit 54ffee0

File tree

3 files changed

+67
-27
lines changed

3 files changed

+67
-27
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
@Nullable

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

+37-13
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
*/
@@ -108,7 +112,12 @@ public <T> T createProjection(Class<T> projectionType, Object source) {
108112
factory.setOpaque(true);
109113
factory.setInterfaces(projectionType, TargetAware.class);
110114

111-
factory.addAdvice(new DefaultMethodInvokingMethodInterceptor());
115+
ProjectionMetadata projectionMetadata = getProjectionMetadata(projectionType);
116+
117+
if (projectionMetadata.hasDefaultMethods) {
118+
factory.addAdvice(defaultMethodInvokingMethodInterceptor.get());
119+
}
120+
112121
factory.addAdvice(new TargetAwareMethodInterceptor(source.getClass()));
113122
factory.addAdvice(getMethodInterceptor(source, projectionType));
114123

@@ -125,8 +134,12 @@ public <T> T createProjection(Class<T> projectionType) {
125134

126135
@Override
127136
public final ProjectionInformation getProjectionInformation(Class<?> projectionType) {
137+
return getProjectionMetadata(projectionType).projectionInformation;
138+
}
128139

129-
return projectionInformationCache.computeIfAbsent(projectionType, this::createProjectionInformation);
140+
private ProjectionMetadata getProjectionMetadata(Class<?> projectionType) {
141+
return projectionInformationCache.computeIfAbsent(projectionType,
142+
it -> ProjectionMetadata.create(it, createProjectionInformation(it)));
130143
}
131144

132145
/**
@@ -193,13 +206,11 @@ private MethodInterceptorFactory getFactoryFor(Object source, Class<?> projectio
193206
*
194207
* @author Oliver Gierke
195208
*/
196-
private static class TargetAwareMethodInterceptor implements MethodInterceptor {
209+
private record TargetAwareMethodInterceptor(Class<?> targetType) implements MethodInterceptor {
197210

198211
private static final Method GET_TARGET_CLASS_METHOD;
199212
private static final Method GET_TARGET_METHOD;
200213

201-
private final Class<?> targetType;
202-
203214
static {
204215
try {
205216
GET_TARGET_CLASS_METHOD = TargetAware.class.getMethod("getTargetClass");
@@ -214,10 +225,9 @@ private static class TargetAwareMethodInterceptor implements MethodInterceptor {
214225
*
215226
* @param targetType must not be {@literal null}.
216227
*/
217-
public TargetAwareMethodInterceptor(Class<?> targetType) {
228+
private TargetAwareMethodInterceptor {
218229

219230
Assert.notNull(targetType, "Target type must not be null");
220-
this.targetType = targetType;
221231
}
222232

223233
@Nullable
@@ -239,7 +249,7 @@ public Object invoke(@SuppressWarnings("null") MethodInvocation invocation) thro
239249
*
240250
* @author Oliver Gierke
241251
*/
242-
private static enum MapAccessingMethodInterceptorFactory implements MethodInterceptorFactory {
252+
private enum MapAccessingMethodInterceptorFactory implements MethodInterceptorFactory {
243253

244254
INSTANCE;
245255

@@ -260,7 +270,7 @@ public boolean supports(Object source, Class<?> targetType) {
260270
*
261271
* @author Oliver Gierke
262272
*/
263-
private static enum PropertyAccessingMethodInvokerFactory implements MethodInterceptorFactory {
273+
private enum PropertyAccessingMethodInvokerFactory implements MethodInterceptorFactory {
264274

265275
INSTANCE;
266276

@@ -274,4 +284,18 @@ public boolean supports(Object source, Class<?> targetType) {
274284
return true;
275285
}
276286
}
287+
288+
/**
289+
* Holder for {@link ProjectionInformation} and whether the target projection type uses {@code default} interface
290+
* methods.
291+
*
292+
* @since 3.1.1
293+
*/
294+
record ProjectionMetadata(boolean hasDefaultMethods, ProjectionInformation projectionInformation) {
295+
296+
public static ProjectionMetadata create(Class<?> projectionType, ProjectionInformation projectionInformation) {
297+
return new ProjectionMetadata(DefaultMethodInvokingMethodInterceptor.hasDefaultMethods(projectionType),
298+
projectionInformation);
299+
}
300+
}
277301
}

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

+26-5
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
import java.util.Optional;
2929

3030
import org.junit.jupiter.api.Test;
31-
31+
import org.springframework.aop.Advisor;
3232
import org.springframework.aop.TargetClassAware;
3333
import org.springframework.aop.framework.Advised;
3434
import org.springframework.test.util.ReflectionTestUtils;
@@ -137,10 +137,10 @@ void returnsAllPropertiesAsInputProperties() {
137137
assertThat(result).hasSize(6);
138138
}
139139

140-
@Test // DATACMNS-655
140+
@Test // DATACMNS-655, GH-2831
141141
void invokesDefaultMethodOnProxy() {
142142

143-
var excerpt = factory.createProjection(CustomerExcerpt.class);
143+
var excerpt = factory.createProjection(CustomerExcerptWithDefaultMethod.class);
144144

145145
var advised = (Advised) ReflectionTestUtils.getField(Proxy.getInvocationHandler(excerpt), "advised");
146146
var advisors = advised.getAdvisors();
@@ -149,6 +149,21 @@ void invokesDefaultMethodOnProxy() {
149149
assertThat(advisors[0].getAdvice()).isInstanceOf(DefaultMethodInvokingMethodInterceptor.class);
150150
}
151151

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

@@ -271,8 +286,7 @@ void supportsOptionalWithProjectionAsReturnTypeIfPresent() {
271286
customer.address.city = "New York";
272287
customer.address.zipCode = "ZIP";
273288

274-
var excerpt = factory.createProjection(CustomerWithOptionalHavingProjection.class,
275-
customer);
289+
var excerpt = factory.createProjection(CustomerWithOptionalHavingProjection.class, customer);
276290

277291
assertThat(excerpt.getFirstname()).isEqualTo("Dave");
278292
assertThat(excerpt.getAddress()).hasValueSatisfying(addressExcerpt -> {
@@ -343,6 +357,13 @@ interface CustomerExcerpt {
343357
Map<String, Object> getData();
344358
}
345359

360+
interface CustomerExcerptWithDefaultMethod extends CustomerExcerpt {
361+
362+
default String getFirstnameAndId() {
363+
return getFirstname() + " " + getId();
364+
}
365+
}
366+
346367
interface AddressExcerpt {
347368

348369
String getZipCode();

0 commit comments

Comments
 (0)