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

TAKETODAY / today-infrastructure / 18154768944

01 Oct 2025 07:26AM UTC coverage: 81.882% (-0.005%) from 81.887%
18154768944

push

github

web-flow
Merge pull request #290 from TAKETODAY/dev/jspecify

jspecify

59788 of 78013 branches covered (76.64%)

Branch coverage included in aggregate %.

141239 of 167496 relevant lines covered (84.32%)

3.6 hits per line

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

81.16
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.logging.Logger;
31
import infra.logging.LoggerFactory;
32
import infra.util.ClassUtils;
33
import infra.util.CollectionUtils;
34
import infra.util.MethodClassKey;
35
import infra.util.ReflectionUtils;
36

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

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

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

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

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

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

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

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

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

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

140
  @Nullable
141
  private Collection<CacheOperation> computeCacheOperations(Method method, @Nullable Class<?> targetClass) {
142
    // Don't allow non-public methods, as configured.
143
    if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
7✔
144
      return null;
2✔
145
    }
146

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

151
    // First try is the method in the target class.
152
    Collection<CacheOperation> opDef = findCacheOperations(specificMethod);
4✔
153
    if (opDef != null) {
2✔
154
      return opDef;
2✔
155
    }
156

157
    // Second try is the caching operation on the target class.
158
    opDef = findCacheOperations(specificMethod.getDeclaringClass());
5✔
159
    if (opDef != null && ClassUtils.isUserLevelMethod(method)) {
5!
160
      return opDef;
2✔
161
    }
162

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

176
    return null;
2✔
177
  }
178

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

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

199
  /**
200
   * Should only public methods be allowed to have caching semantics?
201
   * <p>The default implementation returns {@code false}.
202
   */
203
  protected boolean allowPublicMethodsOnly() {
204
    return false;
×
205
  }
206

207
}
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