Skip to content

Commit 3d7ef3e

Browse files
committed
Avoid storage of null marker per method for proxy decision purposes
Includes missing isCandidateClass support on JCacheOperationSource. Closes gh-20072
1 parent 219004e commit 3d7ef3e

File tree

11 files changed

+300
-133
lines changed

11 files changed

+300
-133
lines changed

Diff for: spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractFallbackJCacheOperationSource.java

+28-14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -27,14 +27,13 @@
2727
import org.springframework.aop.support.AopUtils;
2828
import org.springframework.core.MethodClassKey;
2929
import org.springframework.lang.Nullable;
30+
import org.springframework.util.ReflectionUtils;
3031

3132
/**
32-
* Abstract implementation of {@link JCacheOperationSource} that caches attributes
33+
* Abstract implementation of {@link JCacheOperationSource} that caches operations
3334
* for methods and implements a fallback policy: 1. specific target method;
3435
* 2. declaring method.
3536
*
36-
* <p>This implementation caches attributes by method after they are first used.
37-
*
3837
* @author Stephane Nicoll
3938
* @author Juergen Hoeller
4039
* @since 4.1
@@ -43,35 +42,50 @@
4342
public abstract class AbstractFallbackJCacheOperationSource implements JCacheOperationSource {
4443

4544
/**
46-
* Canonical value held in cache to indicate no caching attribute was
47-
* found for this method and we don't need to look again.
45+
* Canonical value held in cache to indicate no cache operation was
46+
* found for this method, and we don't need to look again.
4847
*/
49-
private static final Object NULL_CACHING_ATTRIBUTE = new Object();
48+
private static final Object NULL_CACHING_MARKER = new Object();
5049

5150

5251
protected final Log logger = LogFactory.getLog(getClass());
5352

54-
private final Map<MethodClassKey, Object> cache = new ConcurrentHashMap<>(1024);
53+
private final Map<MethodClassKey, Object> operationCache = new ConcurrentHashMap<>(1024);
54+
5555

56+
@Override
57+
public boolean hasCacheOperation(Method method, @Nullable Class<?> targetClass) {
58+
return (getCacheOperation(method, targetClass, false) != null);
59+
}
5660

5761
@Override
62+
@Nullable
5863
public JCacheOperation<?> getCacheOperation(Method method, @Nullable Class<?> targetClass) {
64+
return getCacheOperation(method, targetClass, true);
65+
}
66+
67+
@Nullable
68+
private JCacheOperation<?> getCacheOperation(Method method, @Nullable Class<?> targetClass, boolean cacheNull) {
69+
if (ReflectionUtils.isObjectMethod(method)) {
70+
return null;
71+
}
72+
5973
MethodClassKey cacheKey = new MethodClassKey(method, targetClass);
60-
Object cached = this.cache.get(cacheKey);
74+
Object cached = this.operationCache.get(cacheKey);
6175

6276
if (cached != null) {
63-
return (cached != NULL_CACHING_ATTRIBUTE ? (JCacheOperation<?>) cached : null);
77+
return (cached != NULL_CACHING_MARKER ? (JCacheOperation<?>) cached : null);
6478
}
6579
else {
6680
JCacheOperation<?> operation = computeCacheOperation(method, targetClass);
6781
if (operation != null) {
6882
if (logger.isDebugEnabled()) {
6983
logger.debug("Adding cacheable method '" + method.getName() + "' with operation: " + operation);
7084
}
71-
this.cache.put(cacheKey, operation);
85+
this.operationCache.put(cacheKey, operation);
7286
}
73-
else {
74-
this.cache.put(cacheKey, NULL_CACHING_ATTRIBUTE);
87+
else if (cacheNull) {
88+
this.operationCache.put(cacheKey, NULL_CACHING_MARKER);
7589
}
7690
return operation;
7791
}
@@ -84,7 +98,7 @@ private JCacheOperation<?> computeCacheOperation(Method method, @Nullable Class<
8498
return null;
8599
}
86100

87-
// The method may be on an interface, but we need attributes from the target class.
101+
// The method may be on an interface, but we need metadata from the target class.
88102
// If the target class is null, the method will be unchanged.
89103
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
90104

Diff for: spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AnnotationJCacheOperationSource.java

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
2020
import java.lang.reflect.Method;
2121
import java.util.ArrayList;
2222
import java.util.List;
23+
import java.util.Set;
2324

2425
import javax.cache.annotation.CacheDefaults;
2526
import javax.cache.annotation.CacheKeyGenerator;
@@ -32,6 +33,7 @@
3233

3334
import org.springframework.cache.interceptor.CacheResolver;
3435
import org.springframework.cache.interceptor.KeyGenerator;
36+
import org.springframework.core.annotation.AnnotationUtils;
3537
import org.springframework.lang.Nullable;
3638
import org.springframework.util.StringUtils;
3739

@@ -41,10 +43,20 @@
4143
* {@link CacheRemoveAll} annotations.
4244
*
4345
* @author Stephane Nicoll
46+
* @author Juergen Hoeller
4447
* @since 4.1
4548
*/
4649
public abstract class AnnotationJCacheOperationSource extends AbstractFallbackJCacheOperationSource {
4750

51+
private static final Set<Class<? extends Annotation>> JCACHE_OPERATION_ANNOTATIONS =
52+
Set.of(CacheResult.class, CachePut.class, CacheRemove.class, CacheRemoveAll.class);
53+
54+
55+
@Override
56+
public boolean isCandidateClass(Class<?> targetClass) {
57+
return AnnotationUtils.isCandidateClass(targetClass, JCACHE_OPERATION_ANNOTATIONS);
58+
}
59+
4860
@Override
4961
protected JCacheOperation<?> findCacheOperation(Method method, @Nullable Class<?> targetType) {
5062
CacheResult cacheResult = method.getAnnotation(CacheResult.class);
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,15 +16,9 @@
1616

1717
package org.springframework.cache.jcache.interceptor;
1818

19-
import java.io.Serializable;
20-
import java.lang.reflect.Method;
21-
2219
import org.springframework.aop.ClassFilter;
2320
import org.springframework.aop.Pointcut;
2421
import org.springframework.aop.support.AbstractBeanFactoryPointcutAdvisor;
25-
import org.springframework.aop.support.StaticMethodMatcherPointcut;
26-
import org.springframework.lang.Nullable;
27-
import org.springframework.util.ObjectUtils;
2822

2923
/**
3024
* Advisor driven by a {@link JCacheOperationSource}, used to include a
@@ -46,6 +40,7 @@ public class BeanFactoryJCacheOperationSourceAdvisor extends AbstractBeanFactory
4640
* Set the cache operation attribute source which is used to find cache
4741
* attributes. This should usually be identical to the source reference
4842
* set on the cache interceptor itself.
43+
* @see JCacheInterceptor#setCacheOperationSource
4944
*/
5045
public void setCacheOperationSource(JCacheOperationSource cacheOperationSource) {
5146
this.pointcut.setCacheOperationSource(cacheOperationSource);
@@ -64,37 +59,4 @@ public Pointcut getPointcut() {
6459
return this.pointcut;
6560
}
6661

67-
68-
private static class JCacheOperationSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {
69-
70-
@Nullable
71-
private JCacheOperationSource cacheOperationSource;
72-
73-
public void setCacheOperationSource(@Nullable JCacheOperationSource cacheOperationSource) {
74-
this.cacheOperationSource = cacheOperationSource;
75-
}
76-
77-
@Override
78-
public boolean matches(Method method, Class<?> targetClass) {
79-
return (this.cacheOperationSource == null ||
80-
this.cacheOperationSource.getCacheOperation(method, targetClass) != null);
81-
}
82-
83-
@Override
84-
public boolean equals(@Nullable Object other) {
85-
return (this == other || (other instanceof JCacheOperationSourcePointcut that &&
86-
ObjectUtils.nullSafeEquals(this.cacheOperationSource, that.cacheOperationSource)));
87-
}
88-
89-
@Override
90-
public int hashCode() {
91-
return JCacheOperationSourcePointcut.class.hashCode();
92-
}
93-
94-
@Override
95-
public String toString() {
96-
return getClass().getName() + ": " + this.cacheOperationSource;
97-
}
98-
}
99-
10062
}

Diff for: spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/JCacheOperationSource.java

+34-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,16 +25,48 @@
2525
* cache operation attributes from standard JSR-107 annotations.
2626
*
2727
* @author Stephane Nicoll
28+
* @author Juergen Hoeller
2829
* @since 4.1
2930
* @see org.springframework.cache.interceptor.CacheOperationSource
3031
*/
3132
public interface JCacheOperationSource {
3233

34+
/**
35+
* Determine whether the given class is a candidate for cache operations
36+
* in the metadata format of this {@code JCacheOperationSource}.
37+
* <p>If this method returns {@code false}, the methods on the given class
38+
* will not get traversed for {@link #getCacheOperation} introspection.
39+
* Returning {@code false} is therefore an optimization for non-affected
40+
* classes, whereas {@code true} simply means that the class needs to get
41+
* fully introspected for each method on the given class individually.
42+
* @param targetClass the class to introspect
43+
* @return {@code false} if the class is known to have no cache operation
44+
* metadata at class or method level; {@code true} otherwise. The default
45+
* implementation returns {@code true}, leading to regular introspection.
46+
* @since 6.2
47+
* @see #hasCacheOperation
48+
*/
49+
default boolean isCandidateClass(Class<?> targetClass) {
50+
return true;
51+
}
52+
53+
/**
54+
* Determine whether there is a JSR-107 cache operation for the given method.
55+
* @param method the method to introspect
56+
* @param targetClass the target class (can be {@code null}, in which case
57+
* the declaring class of the method must be used)
58+
* @since 6.2
59+
* @see #getCacheOperation
60+
*/
61+
default boolean hasCacheOperation(Method method, @Nullable Class<?> targetClass) {
62+
return (getCacheOperation(method, targetClass) != null);
63+
}
64+
3365
/**
3466
* Return the cache operations for this method, or {@code null}
3567
* if the method contains no <em>JSR-107</em> related metadata.
3668
* @param method the method to introspect
37-
* @param targetClass the target class (may be {@code null}, in which case
69+
* @param targetClass the target class (can be {@code null}, in which case
3870
* the declaring class of the method must be used)
3971
* @return the cache operation for this method, or {@code null} if none found
4072
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cache.jcache.interceptor;
18+
19+
import java.io.Serializable;
20+
import java.lang.reflect.Method;
21+
22+
import org.springframework.aop.ClassFilter;
23+
import org.springframework.aop.support.StaticMethodMatcherPointcut;
24+
import org.springframework.cache.CacheManager;
25+
import org.springframework.lang.Nullable;
26+
import org.springframework.util.ObjectUtils;
27+
28+
/**
29+
* A {@code Pointcut} that matches if the underlying {@link JCacheOperationSource}
30+
* has an operation for a given method.
31+
*
32+
* @author Juergen Hoeller
33+
* @since 6.2
34+
*/
35+
@SuppressWarnings("serial")
36+
final class JCacheOperationSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {
37+
38+
@Nullable
39+
private JCacheOperationSource cacheOperationSource;
40+
41+
42+
public JCacheOperationSourcePointcut() {
43+
setClassFilter(new JCacheOperationSourceClassFilter());
44+
}
45+
46+
47+
public void setCacheOperationSource(@Nullable JCacheOperationSource cacheOperationSource) {
48+
this.cacheOperationSource = cacheOperationSource;
49+
}
50+
51+
@Override
52+
public boolean matches(Method method, Class<?> targetClass) {
53+
return (this.cacheOperationSource == null ||
54+
this.cacheOperationSource.hasCacheOperation(method, targetClass));
55+
}
56+
57+
@Override
58+
public boolean equals(@Nullable Object other) {
59+
return (this == other || (other instanceof JCacheOperationSourcePointcut that &&
60+
ObjectUtils.nullSafeEquals(this.cacheOperationSource, that.cacheOperationSource)));
61+
}
62+
63+
@Override
64+
public int hashCode() {
65+
return JCacheOperationSourcePointcut.class.hashCode();
66+
}
67+
68+
@Override
69+
public String toString() {
70+
return getClass().getName() + ": " + this.cacheOperationSource;
71+
}
72+
73+
74+
/**
75+
* {@link ClassFilter} that delegates to {@link JCacheOperationSource#isCandidateClass}
76+
* for filtering classes whose methods are not worth searching to begin with.
77+
*/
78+
private final class JCacheOperationSourceClassFilter implements ClassFilter {
79+
80+
@Override
81+
public boolean matches(Class<?> clazz) {
82+
if (CacheManager.class.isAssignableFrom(clazz)) {
83+
return false;
84+
}
85+
return (cacheOperationSource == null || cacheOperationSource.isCandidateClass(clazz));
86+
}
87+
88+
@Nullable
89+
private JCacheOperationSource getCacheOperationSource() {
90+
return cacheOperationSource;
91+
}
92+
93+
@Override
94+
public boolean equals(@Nullable Object other) {
95+
return (this == other || (other instanceof JCacheOperationSourceClassFilter that &&
96+
ObjectUtils.nullSafeEquals(getCacheOperationSource(), that.getCacheOperationSource())));
97+
}
98+
99+
@Override
100+
public int hashCode() {
101+
return JCacheOperationSourceClassFilter.class.hashCode();
102+
}
103+
104+
@Override
105+
public String toString() {
106+
return JCacheOperationSourceClassFilter.class.getName() + ": " + getCacheOperationSource();
107+
}
108+
}
109+
110+
}

0 commit comments

Comments
 (0)