From d4b79ea8f284ad176e7342ffab1511c5b46896b7 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Wed, 12 Jul 2017 17:09:39 +0200 Subject: [PATCH] DATACMNS-1112 - Cache instances of ReturnedType to avoid repeated reflection inspection. We now keep instances of ReturnedType around to avoid the repeated reflection inspection for constructor and parameter name detection for class types and projection information for interfaces. --- .../data/repository/query/ReturnedType.java | 20 ++++++++++++++++--- .../query/ReturnedTypeUnitTests.java | 11 ++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/springframework/data/repository/query/ReturnedType.java b/src/main/java/org/springframework/data/repository/query/ReturnedType.java index 97e91df45..567fb6f0c 100644 --- a/src/main/java/org/springframework/data/repository/query/ReturnedType.java +++ b/src/main/java/org/springframework/data/repository/query/ReturnedType.java @@ -18,6 +18,7 @@ package org.springframework.data.repository.query; import lombok.AccessLevel; import lombok.NonNull; import lombok.RequiredArgsConstructor; +import lombok.Value; import java.beans.PropertyDescriptor; import java.util.ArrayList; @@ -25,6 +26,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import org.springframework.data.mapping.PreferredConstructor; @@ -33,6 +35,7 @@ import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.projection.ProjectionInformation; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import org.springframework.util.ConcurrentReferenceHashMap; /** * A representation of the type returned by a {@link QueryMethod}. @@ -44,6 +47,8 @@ import org.springframework.util.ClassUtils; @RequiredArgsConstructor(access = AccessLevel.PRIVATE) public abstract class ReturnedType { + private static final Map CACHE = new ConcurrentReferenceHashMap<>(32); + private final @NonNull Class domainType; /** @@ -60,9 +65,12 @@ public abstract class ReturnedType { Assert.notNull(domainType, "Domain type must not be null!"); Assert.notNull(factory, "ProjectionFactory must not be null!"); - return returnedType.isInterface() - ? new ReturnedInterface(factory.getProjectionInformation(returnedType), domainType) - : new ReturnedClass(returnedType, domainType); + return CACHE.computeIfAbsent(CacheKey.of(returnedType, domainType, factory.hashCode()), key -> { + + return returnedType.isInterface() + ? new ReturnedInterface(factory.getProjectionInformation(returnedType), domainType) + : new ReturnedClass(returnedType, domainType); + }); } /** @@ -316,4 +324,10 @@ public abstract class ReturnedType { return ClassUtils.isPrimitiveOrWrapper(type); } } + + @Value(staticConstructor = "of") + private static class CacheKey { + Class returnedType, domainType; + int projectionFactoryHashCode; + } } diff --git a/src/test/java/org/springframework/data/repository/query/ReturnedTypeUnitTests.java b/src/test/java/org/springframework/data/repository/query/ReturnedTypeUnitTests.java index 75b6c5a07..43fe37498 100755 --- a/src/test/java/org/springframework/data/repository/query/ReturnedTypeUnitTests.java +++ b/src/test/java/org/springframework/data/repository/query/ReturnedTypeUnitTests.java @@ -157,6 +157,17 @@ public class ReturnedTypeUnitTests { assertThat(properties).containsExactly("firstname"); } + @Test // DATACMNS-1112 + public void cachesInstancesBySourceTypes() { + + SpelAwareProxyProjectionFactory factory = new SpelAwareProxyProjectionFactory(); + + ReturnedType left = ReturnedType.of(Child.class, Object.class, factory); + ReturnedType right = ReturnedType.of(Child.class, Object.class, factory); + + assertThat(left).isSameAs(right); + } + private static ReturnedType getReturnedType(String methodName, Class... parameters) throws Exception { return getQueryMethod(methodName, parameters).getResultProcessor().getReturnedType(); }