diff --git a/src/main/java/org/springframework/data/projection/ProxyProjectionFactory.java b/src/main/java/org/springframework/data/projection/ProxyProjectionFactory.java index 95191927e..975ac66ff 100644 --- a/src/main/java/org/springframework/data/projection/ProxyProjectionFactory.java +++ b/src/main/java/org/springframework/data/projection/ProxyProjectionFactory.java @@ -30,6 +30,7 @@ import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import org.springframework.util.ConcurrentReferenceHashMap; /** * A {@link ProjectionFactory} to create JDK proxies to back interfaces and handle method invocations on them. By @@ -46,6 +47,7 @@ class ProxyProjectionFactory implements ProjectionFactory, BeanClassLoaderAware private final List factories; private final ConversionService conversionService; + private final Map, ProjectionInformation> projectionInformationCache = new ConcurrentReferenceHashMap<>(); private ClassLoader classLoader; /** @@ -127,7 +129,18 @@ class ProxyProjectionFactory implements ProjectionFactory, BeanClassLoaderAware * @see org.springframework.data.projection.ProjectionFactory#getProjectionInformation(java.lang.Class) */ @Override - public ProjectionInformation getProjectionInformation(Class projectionType) { + public final ProjectionInformation getProjectionInformation(Class projectionType) { + + return projectionInformationCache.computeIfAbsent(projectionType, this::createProjectionInformation); + } + + /** + * Creates a fresh, cacheable {@link ProjectionInformation} instance for the given projection type. + * + * @param projectionType must not be {@literal null}. + * @return + */ + protected ProjectionInformation createProjectionInformation(Class projectionType) { return new DefaultProjectionInformation(projectionType); } diff --git a/src/main/java/org/springframework/data/projection/SpelAwareProxyProjectionFactory.java b/src/main/java/org/springframework/data/projection/SpelAwareProxyProjectionFactory.java index 07aa06bab..8a28c184a 100644 --- a/src/main/java/org/springframework/data/projection/SpelAwareProxyProjectionFactory.java +++ b/src/main/java/org/springframework/data/projection/SpelAwareProxyProjectionFactory.java @@ -28,6 +28,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.data.util.AnnotationDetectionMethodCallback; import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; /** @@ -68,25 +69,17 @@ public class SpelAwareProxyProjectionFactory extends ProxyProjectionFactory impl protected MethodInterceptor postProcessAccessorInterceptor(MethodInterceptor interceptor, Object source, Class projectionType) { - if (!typeCache.containsKey(projectionType)) { - - AnnotationDetectionMethodCallback callback = new AnnotationDetectionMethodCallback<>(Value.class); - ReflectionUtils.doWithMethods(projectionType, callback); - - typeCache.put(projectionType, callback.hasFoundAnnotation()); - } - - return typeCache.get(projectionType) + return typeCache.computeIfAbsent(projectionType, SpelAwareProxyProjectionFactory::hasMethodWithValueAnnotation) ? new SpelEvaluatingMethodInterceptor(interceptor, source, beanFactory, parser, projectionType) : interceptor; } - /* + /* * (non-Javadoc) - * @see org.springframework.data.projection.ProxyProjectionFactory#getProjectionInformation(java.lang.Class) + * @see org.springframework.data.projection.ProxyProjectionFactory#createProjectionInformation(java.lang.Class) */ @Override - public ProjectionInformation getProjectionInformation(Class projectionType) { + protected ProjectionInformation createProjectionInformation(Class projectionType) { return new DefaultProjectionInformation(projectionType) { @@ -111,4 +104,20 @@ public class SpelAwareProxyProjectionFactory extends ProxyProjectionFactory impl } }; } + + /** + * Returns whether the given type as a method annotated with {@link Value}. + * + * @param type must not be {@literal null}. + * @return + */ + private static boolean hasMethodWithValueAnnotation(Class type) { + + Assert.notNull(type, "Type must not be null!"); + + AnnotationDetectionMethodCallback callback = new AnnotationDetectionMethodCallback<>(Value.class); + ReflectionUtils.doWithMethods(type, callback); + + return callback.hasFoundAnnotation(); + } }