A Spring Boot 4.1.1 module whose test suite is the evidence for the article: 20 tests producing 22 transcripts under docs/output/, plus 12 documentation chapters. Findings the build pins: - @EnableCaching has no exposeProxy attribute; the widely-copied @EnableCaching(exposeProxy = true) does not compile. - Two methods sharing a cache name and an argument type share a key space, and one silently serves the other's answers. - The documented cache-provider detection order does not match CacheType's enum order in 4.1.1: COUCHBASE before INFINISPAN, and CACHE2K before CAFFEINE. - beforeInvocation = true is NOT deferred by TransactionAwareCacheManagerProxy on 7.0.9 - doEvict picks evictIfPresent, which the decorator does not intercept. - Four of five invalid declarations start a clean context and throw at the first call. - Caffeine on the classpath silently displaces the simple provider.
68 lines
2.6 KiB
Markdown
68 lines
2.6 KiB
Markdown
[← transactions](10-transactions.md) · [next: production checklist →](12-production-checklist.md)
|
|
|
|
# 11. Seeing what is actually happening
|
|
|
|
Most caching bugs stop being mysterious the moment you can see the keys. Four things to reach
|
|
for, in order of how quickly they answer the question.
|
|
|
|
## 1. Print the cache
|
|
|
|
The diagnostic endpoint in this module walks the `CacheManager` and dumps every entry with the
|
|
runtime class of the key and the value. `docs/output/23-diagnostics.txt`:
|
|
|
|
```json
|
|
"shapes": {
|
|
"implementation": "org.springframework.cache.concurrent.ConcurrentMapCache",
|
|
"nativeStore": "java.util.concurrent.ConcurrentHashMap",
|
|
"entries": {
|
|
"SimpleKey [] [SimpleKey]": "zero [String]",
|
|
"SimpleKey [abc, 7] [SimpleKey]": "two:abc:7 [String]",
|
|
"abc [String]": "one:abc [String]"
|
|
}
|
|
}
|
|
```
|
|
|
|
A `SimpleKey []` where you expected an id, two methods writing into one key space, or a
|
|
`NullValue` sitting where a record should be — all visible at a glance.
|
|
|
|
**Delete it before shipping.** It exposes cached data over HTTP with no authorisation. If you
|
|
want something permanent, put it behind Actuator's security and return key counts rather than
|
|
values.
|
|
|
|
## 2. Is the bean even proxied?
|
|
|
|
```java
|
|
AopUtils.isAopProxy(bean) // false -> nothing downstream matters
|
|
bean.getClass().getName() // ...$$SpringCGLIB$$0
|
|
AopUtils.getTargetClass(bean)
|
|
```
|
|
|
|
If it is proxied and the cache is still empty, the call is not going through the proxy. See
|
|
[chapter 3](03-self-invocation.md).
|
|
|
|
## 3. Actuator
|
|
|
|
`management.endpoints.web.exposure.include=caches` gives `/actuator/caches`, which lists the
|
|
cache names and their `CacheManager` — enough to confirm which provider is live and whether a
|
|
cache name is a typo. `DELETE /actuator/caches/{name}` clears one, which is a genuinely useful
|
|
operational lever.
|
|
|
|
`/actuator/metrics/cache.gets` and friends are populated automatically for providers Micrometer
|
|
can instrument. **Caffeine only reports statistics if the cache was built with `recordStats()`**
|
|
— without it the metrics exist and read zero, which looks exactly like a cache nobody is using.
|
|
|
|
## 4. Turn the cache off
|
|
|
|
```properties
|
|
spring.cache.type=none
|
|
```
|
|
|
|
A `NoOpCacheManager`: every method runs every time, annotations untouched. If the bug survives,
|
|
it was never the cache. This is the fastest bisect available and it takes one property.
|
|
|
|
## Logging
|
|
|
|
`logging.level.org.springframework.cache=TRACE` logs each operation the interceptor resolves.
|
|
It is noisy enough that it is a debugging tool rather than something to leave on, but it answers
|
|
"did the interceptor see this call at all" definitively.
|