• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

TAKETODAY / today-infrastructure / 19225086188

10 Nov 2025 08:15AM UTC coverage: 84.13%. Remained the same
19225086188

push

github

TAKETODAY
:white_check_mark:

61359 of 78029 branches covered (78.64%)

Branch coverage included in aggregate %.

145112 of 167391 relevant lines covered (86.69%)

3.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.67
today-context/src/main/java/infra/cache/interceptor/AbstractFallbackCacheOperationSource.java
1
/*
2
 * Copyright 2017 - 2025 the original author or authors.
3
 *
4
 * This program is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU General Public License as published by
6
 * the Free Software Foundation, either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see [https://www.gnu.org/licenses/]
16
 */
17

18
package infra.cache.interceptor;
19

20
import org.jspecify.annotations.Nullable;
21

22
import java.lang.reflect.Method;
23
import java.lang.reflect.Modifier;
24
import java.util.Collection;
25
import java.util.Collections;
26
import java.util.Map;
27
import java.util.concurrent.ConcurrentHashMap;
28

29
import infra.aop.support.AopUtils;
30
import infra.beans.factory.Aware;
31
import infra.logging.Logger;
32
import infra.logging.LoggerFactory;
33
import infra.util.ClassUtils;
34
import infra.util.CollectionUtils;
35
import infra.util.MethodClassKey;
36
import infra.util.ReflectionUtils;
37

38
/**
39
 * Abstract implementation of {@link CacheOperation} that caches attributes
40
 * for methods and implements a fallback policy: 1. specific target method;
41
 * 2. target class; 3. declaring method; 4. declaring class/interface.
42
 *
43
 * <p>Defaults to using the target class's caching attribute if none is
44
 * associated with the target method. Any caching attribute associated with
45
 * the target method completely overrides a class caching attribute.
46
 * If none found on the target class, the interface that the invoked method
47
 * has been called through (in case of a JDK proxy) will be checked.
48
 *
49
 * <p>This implementation caches attributes by method after they are first
50
 * used. If it is ever desirable to allow dynamic changing of cacheable
51
 * attributes (which is very unlikely), caching could be made configurable.
52
 *
53
 * @author Costin Leau
54
 * @author Juergen Hoeller
55
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
56
 * @since 4.0
57
 */
58
public abstract class AbstractFallbackCacheOperationSource implements CacheOperationSource {
2✔
59

60
  /**
61
   * Canonical value held in cache to indicate no cache operation was
62
   * found for this method, and we don't need to look again.
63
   */
64
  private static final Collection<CacheOperation> NULL_CACHING_MARKER = Collections.emptyList();
3✔
65

66
  /**
67
   * Logger available to subclasses.
68
   * <p>As this base class is not marked Serializable, the logger will be recreated
69
   * after serialization - provided that the concrete subclass is Serializable.
70
   */
71
  protected final Logger logger = LoggerFactory.getLogger(getClass());
5✔
72

73
  /**
74
   * Cache of CacheOperations, keyed by method on a specific target class.
75
   * <p>As this base class is not marked Serializable, the cache will be recreated
76
   * after serialization - provided that the concrete subclass is Serializable.
77
   */
78
  private final Map<Object, Collection<CacheOperation>> operationCache = new ConcurrentHashMap<>(1024);
7✔
79

80
  @Override
81
  public boolean hasCacheOperations(Method method, @Nullable Class<?> targetClass) {
82
    return CollectionUtils.isNotEmpty(getCacheOperations(method, targetClass, false));
7✔
83
  }
84

85
  @Override
86
  @Nullable
87
  public Collection<CacheOperation> getCacheOperations(Method method, @Nullable Class<?> targetClass) {
88
    return getCacheOperations(method, targetClass, true);
6✔
89
  }
90

91
  /**
92
   * Determine the cache operations for this method invocation.
93
   * <p>Defaults to class-declared metadata if no method-level metadata is found.
94
   *
95
   * @param method the method for the current invocation (never {@code null})
96
   * @param targetClass the target class for this invocation (can be {@code null})
97
   * @param cacheNull whether {@code null} results should be cached as well
98
   * @return {@link CacheOperation} for this method, or {@code null} if the method
99
   * is not cacheable
100
   */
101
  @Nullable
102
  private Collection<CacheOperation> getCacheOperations(Method method, @Nullable Class<?> targetClass, boolean cacheNull) {
103
    if (ReflectionUtils.isObjectMethod(method)) {
3✔
104
      return null;
2✔
105
    }
106

107
    Object cacheKey = getCacheKey(method, targetClass);
5✔
108
    Collection<CacheOperation> cached = this.operationCache.get(cacheKey);
6✔
109

110
    if (cached != null) {
2✔
111
      return (cached != NULL_CACHING_MARKER ? cached : null);
6!
112
    }
113
    else {
114
      Collection<CacheOperation> cacheOps = computeCacheOperations(method, targetClass);
5✔
115
      if (cacheOps != null) {
2✔
116
        if (logger.isTraceEnabled()) {
4!
117
          logger.trace("Adding cacheable method '{}' with attribute: {}", method.getName(), cacheOps);
×
118
        }
119
        operationCache.put(cacheKey, cacheOps);
7✔
120
      }
121
      else if (cacheNull) {
2!
122
        operationCache.put(cacheKey, NULL_CACHING_MARKER);
×
123
      }
124
      return cacheOps;
2✔
125
    }
126
  }
127

128
  /**
129
   * Determine a cache key for the given method and target class.
130
   * <p>Must not produce same key for overloaded methods.
131
   * Must produce same key for different instances of the same method.
132
   *
133
   * @param method the method (never {@code null})
134
   * @param targetClass the target class (may be {@code null})
135
   * @return the cache key (never {@code null})
136
   */
137
  protected Object getCacheKey(Method method, @Nullable Class<?> targetClass) {
138
    return new MethodClassKey(method, targetClass);
6✔
139
  }
140

141
  @Nullable
142
  private Collection<CacheOperation> computeCacheOperations(Method method, @Nullable Class<?> targetClass) {
143
    // Don't allow non-public methods, as configured.
144
    if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
7✔
145
      return null;
2✔
146
    }
147
    // Skip methods declared on BeanFactoryAware and co.
148
    if (method.getDeclaringClass().isInterface() && Aware.class.isAssignableFrom(method.getDeclaringClass())) {
9✔
149
      return null;
2✔
150
    }
151

152
    // The method may be on an interface, but we need metadata from the target class.
153
    // If the target class is null, the method will be unchanged.
154
    Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
4✔
155

156
    // First try is the method in the target class.
157
    Collection<CacheOperation> opDef = findCacheOperations(specificMethod);
4✔
158
    if (opDef != null) {
2✔
159
      return opDef;
2✔
160
    }
161

162
    // Second try is the caching operation on the target class.
163
    opDef = findCacheOperations(specificMethod.getDeclaringClass());
5✔
164
    if (opDef != null && ClassUtils.isUserLevelMethod(method)) {
5!
165
      return opDef;
2✔
166
    }
167

168
    if (specificMethod != method) {
3✔
169
      // Fallback is to look at the original method.
170
      opDef = findCacheOperations(method);
4✔
171
      if (opDef != null) {
2!
172
        return opDef;
×
173
      }
174
      // Last fallback is the class of the original method.
175
      opDef = findCacheOperations(method.getDeclaringClass());
5✔
176
      if (opDef != null && ClassUtils.isUserLevelMethod(method)) {
2!
177
        return opDef;
×
178
      }
179
    }
180

181
    return null;
2✔
182
  }
183

184
  /**
185
   * Subclasses need to implement this to return the cache operations for the
186
   * given class, if any.
187
   *
188
   * @param clazz the class to retrieve the cache operations for
189
   * @return all cache operations associated with this class, or {@code null} if none
190
   */
191
  @Nullable
192
  protected abstract Collection<CacheOperation> findCacheOperations(Class<?> clazz);
193

194
  /**
195
   * Subclasses need to implement this to return the cache operations for the
196
   * given method, if any.
197
   *
198
   * @param method the method to retrieve the cache operations for
199
   * @return all cache operations associated with this method, or {@code null} if none
200
   */
201
  @Nullable
202
  protected abstract Collection<CacheOperation> findCacheOperations(Method method);
203

204
  /**
205
   * Should only public methods be allowed to have caching semantics?
206
   * <p>The default implementation returns {@code false}.
207
   */
208
  protected boolean allowPublicMethodsOnly() {
209
    return false;
×
210
  }
211

212
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc