diff --git a/README.md b/README.md
index 2f400ade..fdf2b9d4 100644
--- a/README.md
+++ b/README.md
@@ -1,92 +1,136 @@
# ResiCache
-**A protection-enhancement annotation ecosystem for Spring Cache** — beyond
-`@Cacheable`, use a single `@RedisCacheable` annotation to add cache-penetration,
-cache-breakdown, cache-avalanche, and hot-key early-refresh defenses to your
-Redis cache. Protection is injected through a composable responsibility chain,
-without re-inventing AOP.
+**Protection-in-depth for Spring Cache on Redis.** ResiCache adds declarative
+cache-penetration, cache-breakdown, cache-avalanche, and hot-key refresh
+protection through a composable responsibility chain, while keeping Spring
+Cache as the application-facing model.
[](https://github.com/davidhlp/ResiCache/actions/workflows/ci.yml)
[](LICENSE)
-> **Project status: early (v0.0.2) · Non-SLA best-effort · solo-maintained.**
-> Read [⚠️ Known Limitations](#known-limitations) before any production use.
+[English](README.md) · [简体中文](README.zh-CN.md)
+
+> [!WARNING]
+> ResiCache is pre-1.0 (`v0.0.2`), non-SLA, and currently maintained by one
+> maintainer. The current `main` line targets Spring Boot 4.0 and Java 21, but
+> it does not have a matching Maven Central artifact yet. Read
+> [Compatibility](COMPATIBILITY.md) and [Limitations](#limitations) before
+> adopting it in production.
+
+## Contents
+
+- [Overview](#overview)
+- [Features](#features)
+- [Compatibility and requirements](#compatibility-and-requirements)
+- [Quick start](#quick-start)
+- [How it works](#how-it-works)
+- [Configuration](#configuration)
+- [Extension points](#extension-points)
+- [Runtime semantics](#runtime-semantics)
+- [Comparison](#comparison)
+- [Limitations](#limitations)
+- [Not in scope](#not-in-scope)
+- [Project layout](#project-layout)
+- [Development](#development)
+- [Project status and support](#project-status-and-support)
+- [Security](#security)
+- [License](#license)
+
+## Overview
+
+Spring Cache provides a consistent programming model for caching, but it does
+not by itself provide protection against cache penetration, breakdown, avalanche,
+or hot-key expiry. ResiCache adds those protections as explicit capabilities on
+top of Spring Cache and Redis.
+
+ResiCache is designed to:
+
+- **Keep Spring Cache at the boundary**: applications still use
+ `@EnableCaching`, cache operations, and Spring's cache abstractions.
+- **Make protection composable**: each protection mechanism is a typed handler
+ in one ordered chain rather than unrelated advice scattered across services.
+- **Fail closed at important boundaries**: missing distributed-lock support,
+ unsafe serialization, and invalid configuration are observable instead of
+ silently becoming weaker guarantees.
+- **Stay focused**: it is not a replacement for a multi-level cache,
+ circuit-breaker, rate-limiter, or reactive cache framework.
-[简体中文](README.zh-CN.md)
-
-## What it is
+## Features
-Spring Cache (`@Cacheable` / `@CachePut` / `@CacheEvict`) solves "caching", not
-"protection" — cache penetration, breakdown, avalanche, and hot-key expiry are
-left to the business layer. ResiCache turns these defenses into declarative
-capabilities via **`@RedisCacheable` enhancement annotations** and a
-**composable responsibility chain**.
+| Capability | What it provides |
+|---|---|
+| Bloom filter | Rejects requests for keys that are not known to exist, reducing cache penetration |
+| Distributed lock | Redisson-based coordination for cache breakdown protection |
+| TTL jitter | Randomizes expiration to reduce synchronized cache avalanche |
+| Null-value caching | Caches negative lookups when explicitly enabled |
+| Early expiration | Refreshes hot keys before their normal expiration boundary |
+| Composable handler chain | Orders protection mechanisms through `HandlerOrder` and `@HandlerPriority` |
+| Safe serialization | Uses a controlled deserialization whitelist and an internal wire envelope |
+| Spring Cache integration | Works with Spring Cache instead of introducing a second AOP model |
-- **Coexists with Spring Cache**: extends `RedisCacheManager` /
- `CacheInterceptor` — does not replace `@EnableCaching`, does not re-invent AOP.
-- **Difference from JetCache**: JetCache focuses on **multi-level caching**;
- ResiCache focuses on **cache-defense-in-depth** — every handler on the chain is
- pluggable and composable, which JetCache does not offer.
+All five protection attributes on `@RedisCacheable` default to `false`; enable
+the mechanisms that match the application's risk profile.
-## Features
+## Compatibility and requirements
-| Feature | Description |
-|---------|-------------|
-| **Bloom filter** | Prevents cache penetration; blocks non-existent keys |
-| **Distributed lock** | Redisson-based; prevents cache breakdown (**requires Redisson on classpath**) |
-| **TTL jitter** | Randomizes TTL; prevents cache avalanche |
-| **Null-value caching** | Caches `null`; prevents penetration |
-| **Early expiration** | Async early refresh for hot keys; improves hit rate |
-| **Composable chain** | Handlers strung together by priority; custom handlers can be inserted (differentiator) |
-| **Safe serialization** | Whitelisted deserialization; defends against Jackson polymorphic-type attacks |
+The repository currently ships one supported build line:
-> ResiCache does **not** provide circuit breaking / rate limiting / multi-level
-> local cache / Reactive support — see [Not in Scope](#not-in-scope).
+| Component | Current `main` line |
+|---|---:|
+| Java | 21 |
+| Spring Boot | 4.0.0 |
+| Spring Framework / Spring Cache | 7.x, through Boot 4 |
+| Spring Data Redis | 4.0.x |
+| Redis Server | 7.x |
+| Redisson | 3.50.0, optional |
+| Caffeine | 3.1.8, bundled for internal support |
+| Docker | Required for Testcontainers-backed verification |
+| Testcontainers | 1.20.6, test scope |
-## Architecture
+See the complete matrix and known compatibility boundaries in
+[COMPATIBILITY.md](COMPATIBILITY.md).
-ResiCache uses a **responsibility chain** for cache-operation protection. Handler
-ordering is defined in a single source of truth, the `HandlerOrder` enum, bound
-via `@HandlerPriority`:
+### Published artifact status
-```
-┌─────────────────────────────────────────────────────────────┐
-│ CacheHandlerChain │
-├─────────────────────────────────────────────────────────────┤
-│ ① BloomFilter (100) ── Bloom filter, anti-penetration │
-│ ② SyncLock (200) ── Distributed lock, anti-breakdown│
-│ ③ EarlyExpiration (250) ── Early expiry, hot-key guard │
-│ ④ TTL (300) ── TTL jitter, anti-avalanche │
-│ ⑤ NullValue (400) ── Null caching, anti-penetration │
-│ ⑥ ActualCache (500) ── Actual Redis write │
-└─────────────────────────────────────────────────────────────┘
-```
+The current Boot 4 / Java 21 line is source-first and has not been published to
+Maven Central. The `io.github.davidhlp:ResiCache:0.0.2` coordinate is a
+historical Boot 3 / Java 17 artifact; do not use it as a dependency for the
+current `main` line.
-Each handler returns a typed `HandlerResult` carrying explicit `FlowControl`
-(`CONTINUE`, `SKIP_ALL`, `TERMINATE`) to govern execution without hidden state.
-Handlers opting into post-processing (e.g. Bloom filter async backfilling)
-override `requiresPostProcess` and `afterChainExecution`.
-Built-in handlers are registered by the library's auto-configuration, which
-scans only the library-internal `cache` runtime package. A host application's
-custom handler must be a host-scanned `@Component` or an application `@Bean`,
-implement `CacheHandler`, and use `@HandlerPriority`; the library's internal
-scan does not see host packages.
+To try the current line, [build it from source](#development) or use a release
+that explicitly declares its supported compatibility line.
## Quick start
-### 1. Add the dependency
+### 1. Build and install the current source line locally
+
+```bash
+git clone https://github.com/davidhlp/ResiCache.git
+cd ResiCache
+./mvnw -Punit test -B
+./mvnw install -DskipTests -B
+```
+
+The first command is the no-Docker contributor check. The second installs the
+current source checkout into the local Maven repository for a local consumer;
+it does not publish an artifact. Do not confuse that local build with the
+historical `0.0.2` artifact on Maven Central.
+
+For a local consumer application, add the current checkout's coordinates to
+that application's `pom.xml`:
```xml
io.github.davidhlp
ResiCache
- 0.0.2
+ 0.0.2
```
-> The `0.0.2` artifact on Maven Central is the **earlier Boot 3 / Java 17
-> line** (verified 2026-09-05). The current Boot 4 / Java 21 line has no
-> published artifact yet; the coordinate above is the planned one.
+The dependency resolves from the local Maven repository. Continue with the
+configuration and application examples below in that consumer application,
+not in the ResiCache checkout. The full Redis and Redis Cluster verification
+command is documented in [Development](#development).
### 2. Configure Redis
@@ -96,13 +140,33 @@ spring:
redis:
host: localhost
port: 6379
+ # timeout: 2s
+resi-cache:
+ redis:
+ mode: single
+ host: localhost
+ port: 6379
+ database: 0
+ tls-enabled: false
```
-> ResiCache activates via Spring Boot auto-configuration (entry point
-> `RedisCacheAutoConfiguration`, see `META-INF/spring/...AutoConfiguration.imports`).
-> No extra `@EnableXxx` is required.
+`spring.data.redis.*` and `resi-cache.redis.*` configure separate clients.
+`spring.data.redis.*` configures the Spring Data Redis connection factory used
+for ResiCache cache I/O. `resi-cache.redis.*` configures the Redisson deployment
+used by distributed locking and synchronization; `resi-cache.redisson.*`
+controls its pool, timeout, and retry settings. The `resi-cache.redis.*` defaults
+are `localhost:6379` and database `0`, so omitted endpoint fields do not inherit
+`spring.data.redis.*`. In single mode, an absent `resi-cache.redis.password` may
+fall back to the Spring Data Redis password. For single, cluster, or sentinel
+deployments, configure matching endpoints or topology explicitly in both
+namespaces when using `sync=true`; do not rely on one namespace to configure the
+other.
+
+ResiCache is discovered through Spring Boot auto-configuration via
+`RedisCacheAutoConfiguration`. It does not add `@EnableCaching` for the
+application; the application remains responsible for enabling Spring Cache.
-### 3. Enable caching
+### 3. Enable Spring Cache
```java
@SpringBootApplication
@@ -114,65 +178,125 @@ public class Application {
}
```
-### 4. Use the annotations
-
-**Recommended: `@RedisCacheable` (the protection entry point)**
+### 4. Use the protected annotation
```java
@Service
public class UserService {
- @RedisCacheable(value = "users", key = "#id",
- useBloomFilter = true, // Bloom filter, anti-penetration
- cacheNullValues = true, // null caching
- randomTtl = true, // TTL jitter, anti-avalanche
- variance = 0.2, // jitter amplitude ±20%
- enableEarlyExpiration = true) // hot-key early refresh
+
+ @RedisCacheable(
+ value = "users",
+ key = "#id",
+ cacheNullValues = true,
+ randomTtl = true,
+ variance = 0.2,
+ enableEarlyExpiration = true)
public User getUserById(Long id) {
return userRepository.findById(id);
}
}
```
-**Compatible: `@Cacheable` (no protection)**
+The protection attributes are deliberately explicit. A plain `@Cacheable`
+remains compatible with Spring Cache, but it does not gain ResiCache protection
+in the default `nativeAnnotationMode=SELECTIVE` mode:
```java
-@Cacheable(value = "users", key = "#id") // coexists, but gains no protection
-public User getUserById(Long id) { ... }
+@Cacheable(value = "users", key = "#id")
+public User getUserById(Long id) {
+ // Spring's native cache path; use @RedisCacheable for protection.
+}
+```
+
+## How it works
+
+### Handler chain
+
+Protection is assembled as a responsibility chain. The order is defined by the
+`HandlerOrder` enum and attached to handlers through `@HandlerPriority`:
+
+```text
+┌─────────────────────────────────────────────────────────────┐
+│ CacheHandlerChain │
+├─────────────────────────────────────────────────────────────┤
+│ ① BloomFilter (100) ── Bloom filter, anti-penetration │
+│ ② SyncLock (200) ── Distributed lock, anti-breakdown│
+│ ③ EarlyExpiration (250) ── Early expiry, hot-key guard │
+│ ④ TTL (300) ── TTL jitter, anti-avalanche │
+│ ⑤ NullValue (400) ── Null caching, anti-penetration │
+│ ⑥ ActualCache (500) ── Actual Redis write │
+└─────────────────────────────────────────────────────────────┘
```
-> `@Cacheable` coexists with ResiCache but **gains no protection** — the
-> protection attributes (`useBloomFilter` / `randomTtl` / ...) live only on
-> `@RedisCacheable`. In the current unreleased contract, the default
-> `nativeAnnotationMode=SELECTIVE` means plain `@Cacheable` is handled
-> entirely by Spring's native cache infrastructure. Use `@RedisCacheable` for
-> protection.
+Each handler returns a typed `HandlerResult` with explicit `FlowControl`
+decisions (`CONTINUE`, `SKIP_ALL`, or `TERMINATE`). A handler can opt into
+post-processing through `requiresPostProcess` and
+`afterChainExecution`; the chain engine owns progression and keeps the main
+path separate from isolated post-processing failures.
+
+Built-in handlers are registered by the library's auto-configuration, which
+scans the library-internal runtime package. A host application's custom handler
+must be discovered by the host application as an `@Component` or `@Bean`; the
+library's internal scan does not scan host packages.
+
+### Protection boundaries
+
+- **Cache penetration**: the Bloom filter treats a missing membership bit as a
+ definite miss. The default implementation is populated after successful cache
+ writes; it does not scan or rebuild from the data source. Before enabling it
+ for a cache whose keys are not already in the filter, seed or maintain the
+ public `BloomIFilter` seam.
+- **Cache breakdown**: the sync handler coordinates concurrent loads through a
+ distributed lock when Redisson or another `LockManager` is available.
+- **Cache avalanche**: TTL jitter spreads expiration times when `randomTtl=true`.
+- **Hot-key expiry**: early expiration schedules refresh work before the normal
+ expiry boundary when explicitly enabled.
+- **Negative lookups**: null-value caching can retain an absent result when the
+ annotation or configuration enables it.
## Configuration
-Most properties use the `resi-cache.*` prefix and bind to
-`RedisProCacheProperties`; the four `resi-cache.bloom.*` implementation keys
-are explicitly bound by auto-configuration and described in additional
-configuration metadata.
+Most settings use the `resi-cache.*` prefix and bind to
+`RedisProCacheProperties`. Configuration is resolved during startup; changing
+protection switches requires an application restart.
-### Master switches (current unreleased contract)
+### Enablement and protection switches
```yaml
resi-cache:
- enabled: true # master kill-switch; false disables ResiCache entirely
+ enabled: true
+ native-annotation-mode: SELECTIVE # FULL | NONE | SELECTIVE
protection:
- enabled: true # false skips bloom/lock/early-exp/null-value; TTL preserved
- bloom-filter-enabled: null # per-mechanism overrides, resolved once at startup:
- sync-lock-enabled: null # null inherits the total switch; false disables that
- early-expiration-enabled: null # mechanism only; true cannot re-enable a mechanism
- null-value-enabled: null # when the total switch is false. Restart to apply.
+ enabled: true
+ bloom-filter-enabled: null # null inherits protection.enabled
+ sync-lock-enabled: null
+ early-expiration-enabled: null
+ null-value-enabled: null
```
-Protection toggles are **startup-only**: the handler chain is built once and
-cached; per-mechanism `true` cannot override a `false` total switch, and
-changing protection configuration requires an application restart. TTL and
-the actual-cache handler always remain.
-
-### Global
+`resi-cache.enabled=false` disables the library auto-configuration. The
+`protection.enabled` switch disables Bloom, sync-lock, early-expiration, and
+null-value protection while keeping the base TTL and actual-cache handlers.
+Per-mechanism `true` cannot re-enable a mechanism when the total protection
+switch is `false`.
+
+`native-annotation-mode` controls Spring's native cache annotations:
+
+- `SELECTIVE` (default): pure-native methods with no ResiCache annotation remain
+ on Spring's native path and are not converted by the ResiCache operation
+ source. When a ResiCache annotation is present, a matching native operation
+ is skipped by ResiCache's converter (internal conversion de-duplication).
+ An explicitly mixed method can still be seen by both the ResiCache advisor
+ and Spring's native cache advisor; do not mix annotations on one method
+ without testing the result.
+- `FULL`: converts all supported Spring cache annotations, including plain
+ native-only methods. When `@EnableCaching` or another Spring native advisor
+ is active, those methods can also be handled by both advisors; enable `FULL`
+ only when that duplicate interception or execution is intentional and tested.
+- `NONE`: ignores native Spring cache annotations in the ResiCache operation
+ source.
+
+### Global settings
```yaml
resi-cache:
@@ -183,9 +307,17 @@ resi-cache:
### Metrics and health
-Cache metrics require `resi-cache.metrics.enabled=true` (default OFF) and a
-`MeterRegistry`. The health indicator uses the same explicit property and
-requires Actuator.
+Metrics are opt-in and require both the property and an available
+`MeterRegistry`:
+
+```yaml
+resi-cache:
+ metrics:
+ enabled: true
+```
+
+The Redis cache health indicator uses the same explicit property and requires
+Spring Boot Actuator.
### Bloom filter
@@ -197,6 +329,7 @@ resi-cache:
hash-functions: 3
hash-cache-size: 10000
```
+These settings are bound under the `resi-cache.bloom.*` prefix.
### Distributed lock
@@ -206,172 +339,270 @@ resi-cache:
timeout: 3000
unit: MILLISECONDS
prefix: "cache:lock:"
- local-only: false # true = accept single-JVM sync when Redisson absent (else fail-fast)
+ local-only: false
```
-### Early expiration (hot-key)
+`sync=true` requires a distributed lock implementation such as Redisson. With
+`local-only=false` (the default), missing distributed support fails closed
+instead of silently claiming multi-instance protection. Set `local-only=true`
+only when single-JVM degradation is an explicit, acceptable choice.
+
+### Early expiration
```yaml
resi-cache:
protection:
- early-expiration-enabled: true # optional mechanism override
+ early-expiration-enabled: true
early-expiration:
pool-size: 2
max-pool-size: 10
queue-capacity: 100
```
+### Redis deployment
+
+```yaml
+resi-cache:
+ redis:
+ mode: single # single | cluster | sentinel
+ host: localhost
+ port: 6379
+ database: 0
+ tls-enabled: false
+ # cluster-nodes: [host1:6379, host2:6379]
+ # sentinel-master: mymaster
+ # sentinel-nodes: [host1:26379]
+```
+
+The deployment validator checks mode-specific fields during configuration
+binding. `resi-cache.redis.*` is the Redisson deployment path; `spring.data.redis.*`
+configures the separate connection factory used for cache I/O. The
+`resi-cache.redis.host`, `port`, and `database` defaults are `localhost`, `6379`,
+and `0`, so omitted endpoint fields do not adopt Spring Data Redis values. In
+single mode, an absent `resi-cache.redis.password` may fall back to the Spring
+Data Redis password. For single, cluster, or sentinel deployments, explicitly
+configure matching endpoints or topology in both namespaces when using
+`sync=true`; do not rely on one namespace to configure the other. The
+`resi-cache.redisson.*` namespace controls Redisson pool, timeout, and retry
+settings. Credentials, when needed, belong in the application's secret
+management system rather than in a committed README snippet.
+
### Serialization safety
```yaml
resi-cache:
serializer:
type-property: "@class"
- polymorphic-typing-enabled: false # off by default, safer
+ polymorphic-typing-enabled: false
fail-on-unknown-type: true
- allowed-package-prefixes: # deserialization whitelist
- - "io.github.davidhlp."
- - "com.example." # ← you MUST add your own business packages
+ allowed-package-prefixes:
+ - "io.github.davidhlp"
+ - "com.example.*"
```
-> ⚠️ The whitelist defaults to **only** `io.github.davidhlp` (a literal prefix
-> using the existing `startsWith` semantics). To enforce a package boundary,
-> configure `io.github.davidhlp.*`; custom business types (e.g. `com.example.User`)
-> still require an explicit entry in `allowed-package-prefixes`, otherwise
-> deserialization throws.
->
-> **Wildcard form (current unreleased):** any prefix ending in `.*` is a wildcard
-> sentinel — it matches the class directly (`com.example.Foo`), all sub-package
-> classes (`com.example.sub.Bar`, `com.example.foo.bar.baz.Qux`, …), and is
-> dot-boundary protected (so `com.example.*` does **not** match `com.exampleX.Foo`).
-> Use it when you want to allow a whole package subtree without listing each
-> sub-package.
->
-> ```yaml
-> resi-cache:
-> serializer:
-> allowed-package-prefixes:
-> - "io.github.davidhlp."
-> - "com.example.*" # entire com.example subtree in one entry
-> ```
-
-### Annotation attributes (`@RedisCacheable`)
-
-| Attribute | Default | Description |
-|-----------|---------|-------------|
-| `ttl` | 60 | Cache TTL (seconds) |
-| `cacheNullValues` | false | Cache `null` |
-| `useBloomFilter` | false | Enable Bloom filter |
-| `expectedInsertions` | 100000 | Bloom expected insertions |
-| `falseProbability` | 0.01 | Bloom false-positive rate |
-| `randomTtl` | false | Enable TTL jitter |
-| `variance` | 0.2 | TTL jitter amplitude |
-| `enableEarlyExpiration` | false | Enable early expiry |
-| `earlyExpirationThreshold` | 0.3 | Early-expiry threshold (remaining TTL ratio) |
-| `sync` / `syncTimeout` | false / 10 | Sync wait & timeout |
-
-> The five protection attributes default to **`false`** — enable each explicitly
-> on `@RedisCacheable`. `sync=true` (anti-breakdown) requires Redisson on the
-> classpath; **without it, ResiCache fails fast** (refuses to silently degrade to
-> a single-JVM lock, which is useless across instances). For an explicit
-> single-instance/test degradation, set `resi-cache.sync-lock.local-only=true`.
+The default whitelist is the literal prefix `io.github.davidhlp`, so
+applications should add their own packages explicitly. For dot-boundary subtree
+matching, use a prefix ending in `.*`; for example, `com.example.*` matches
+`com.example.User` and `com.example.orders.Order`, but not `com.exampleX.User`.
-## How it works
+### Per-cache overrides
-**Cache penetration** — the Bloom filter intercepts requests for non-existent
-keys before the cache layer. **Cache breakdown** — a distributed lock ensures
-only one request loads the data. **Cache avalanche** — TTL randomization
-(`TTL = baseTtl ± variance × baseTtl` when `randomTtl=true`) avoids mass
-simultaneous expiry.
+```yaml
+resi-cache:
+ caches:
+ users:
+ ttl: 10m
+ cache-null-values: true
+ key-prefix: "users:"
+```
+
+### `@RedisCacheable` attributes
+
+| Attribute | Default | Purpose |
+|---|---:|---|
+| `ttl` | `60` | Cache TTL in seconds |
+| `cacheNullValues` | `false` | Cache `null` results |
+| `useBloomFilter` | `false` | Enable Bloom-filter protection |
+| `expectedInsertions` | `100000` | Expected Bloom-filter insertions |
+| `falseProbability` | `0.01` | Bloom false-positive target |
+| `randomTtl` | `false` | Enable TTL jitter |
+| `variance` | `0.2` | TTL jitter amplitude |
+| `enableEarlyExpiration` | `false` | Enable hot-key early refresh |
+| `earlyExpirationThreshold` | `0.3` | Remaining-TTL ratio that triggers refresh |
+| `sync` / `syncTimeout` | `false` / `10` | Enable synchronized loading and its wait timeout |
+
+## Extension points
+
+### Annotation family
+
+The public annotation family mirrors Spring Cache operations:
+
+- `@RedisCacheable` for read-through caching and protection attributes.
+- `@RedisCachePut` for explicit cache writes.
+- `@RedisCacheEvict` for cache removal.
+- `@RedisCaching` for grouping multiple ResiCache operations on one method or
+ type.
+
+`@RedisCaching` can expose operations at type level, but the protection-policy
+fields inside its composed annotations are evaluated at method level. A
+type-level declaration does not apply those fields to otherwise unannotated
+methods; repeat the relevant `@RedisCacheable`, `@RedisCachePut`, or
+`@RedisCacheEvict` at method level when the method needs that policy.
+
+### Custom handlers
+
+A custom handler implements the public `CacheHandler` contract and uses
+`@HandlerPriority` with a `HandlerOrder` value. It must be registered in the
+host application's component scan or supplied as an application bean. The
+handler can return a typed `HandlerResult` and can opt into the post-processing
+hooks without managing a linked-list successor itself.
+
+Only the documented public seams are intended for replacement. See
+[STABILITY.md](STABILITY.md) before depending on a public type or changing
+handler behavior.
+
+## Runtime semantics
+
+### Cache I/O failure behavior
+
+| Operation | Behavior |
+|---|---|
+| GET | Degrades to a cache miss and records the internal failure |
+| PUT / PUT_IF_ABSENT / CLEAN | Throws a typed runtime failure retaining the original cause |
+| REMOVE | Observable best-effort removal; does not throw for the removal failure |
+| `get(key, loader)` with a successful loader | Returns the loader value even if write-back fails; write-back failure is logged without the raw key |
+| `get(key, loader)` with a failed loader | Surfaces Spring's `Cache.ValueRetrievalException` |
+
+`@CacheEvict(allEntries=true)` / CLEAN is best-effort and non-atomic, using a
+SCAN cursor and batched deletion. Bloom membership bits are a separately
+maintained set, populated by successful writes by default; they are not
+automatically rebuilt from the data source. Clearing cache entries does not
+remove those bits.
+
+### Serialization migration
+
+ResiCache stores values in an internal `{version, payload}` envelope. It is not
+wire-compatible with Spring's `GenericJackson2JsonRedisSerializer` or
+`JdkSerializer`. Existing applications should use a bounded
+shadow-read → dual-write → cutover migration instead of assuming an in-place
+serializer swap is safe. See the migration guidance in
+[COMPATIBILITY.md](COMPATIBILITY.md).
## Comparison
-ResiCache is one of four common options for caching on top of Redis: JetCache,
-Caffeine, raw Redisson, and ResiCache. The project ships under one line:
-**"ResiCache for Redisson — the declarative cache protection chain Redisson
-forgot to ship"**.
+ResiCache is intentionally narrower than general-purpose caching frameworks:
| Capability | JetCache | Caffeine | Raw Redisson | **ResiCache** |
-|------------|:--------:|:--------:|:------------:|:-------------:|
-| Multi-level local + remote cache | ✅ | local only | — | — |
-| Bloom filter (anti-penetration) | — | — | manual | ✅ |
-| TTL jitter (anti-avalanche) | — | — | manual | ✅ |
-| Distributed breakdown lock | — | — | manual | ✅ |
-| Null-value caching | — | — | manual | ✅ |
-| Hot-key early refresh | — | — | manual | ✅ |
-| Declarative `@Annotation` chain | partial | — | — | ✅ |
-| Broadcast invalidation | ✅ | — | — | — |
-
-**Headline takeaway: the 3 protections JetCache is missing, in one
-Redisson-native chain** — bloom-filter (penetration), TTL jitter (avalanche),
-and distributed breakdown lock (breakdown). ResiCache is the Redisson
-companion that closes those gaps; JetCache is the multi-level / broadcast
-invalidator. The two are complementary in scope, not direct substitutes.
-
-## Known Limitations
-
-- **Protection off by default**: the five protection attributes default `false`;
- enable each explicitly on `@RedisCacheable`.
-- **Serialization envelope incompatible with Spring native**: ResiCache uses a
- `{version, payload}` envelope, incompatible with Spring's
- `GenericJackson2JsonRedisSerializer` / `JdkSerializer` — **existing projects
- must migrate**, otherwise the entire cache misses on cutover.
-- **Serialization whitelist defaults to the author's package**:
- `allowed-package-prefixes` defaults to `io.github.davidhlp.`; custom types must
- be added explicitly (see [Serialization safety](#serialization-safety)).
-- **`nativeAnnotationMode` defaults to `SELECTIVE`**: plain `@Cacheable` is
- handled entirely by Spring's native cache infrastructure, removing the
- dual-advisor risk. Use `@RedisCacheable` for protection.
-- **Cache I/O failure semantics**: GET degrades to a miss and logs the
- failure; PUT, PUT_IF_ABSENT, and CLEAN throw a typed runtime failure
- retaining the original cause; REMOVE is observable best-effort and does not
- throw. **Read-through (`get(key, loader)`) is availability-first**: the
- loader's successful value is always returned — a cache write-back failure
- after a successful load is logged (redacted) and never overrides the value;
- loader failures still surface as Spring `Cache.ValueRetrievalException`.
-- **`@CacheEvict(allEntries=true)` (CLEAN) is best-effort, not atomic** — parity
- with Spring's native `RedisCache.clear` / `DefaultRedisCacheWriter.clean` —
- it uses a SCAN cursor + batched UNLINK/DEL, so keys written mid-CLEAN may be
- stranded and the cache is briefly half-deleted on large key sets. Lua/MULTI
- atomicity is intentionally not used (Redis single-thread O(keyspace) block,
- Cluster cross-slot). Bloom is a data-source existence hint: CLEAN removes
- cache entries but preserves existing Bloom bits, so it can only introduce
- false-positives and never block a valid loader with a false-negative.
-
-## Not in Scope
-
-ResiCache deliberately omits these to avoid bloat — pair with dedicated tools:
-
-- **Circuit breaking / rate limiting** → [Resilience4j](https://resilience4j.readthedocs.io/)
-- **Multi-level local + remote cache** → [Caffeine](https://github.com/ben-manes/caffeine) for the local tier
-- **Reactive caching** (WebFlux) → not supported
-
-## Dependencies
-
-| Dependency | Version |
-|------------|---------|
-| Spring Boot | 4.0.0 (parent) |
-| Java | 21 |
-| Redisson | 3.50.0 (optional) |
-| Caffeine | 3.1.8 |
-| Testcontainers | 1.20.6 |
-
-Full matrix: [COMPATIBILITY.md](COMPATIBILITY.md).
-
-## Project status & maintenance
-
-- **Version**: v0.0.2 — Semantic Versioning < 1.0; APIs may change in minor
- releases; breaking items are marked ⚠️ in [CHANGELOG.md](CHANGELOG.md).
-- **Maintenance**: solo-maintained ([DavidHLP](https://github.com/davidhlp)),
- **Non-SLA best-effort** — no guaranteed response time, but issues are actively
- addressed.
-- **Contributing**: PRs welcome — see [CONTRIBUTING.md](CONTRIBUTING.md).
-- **Performance Benchmarks**: JMH baseline results & SLOs — see [PERFORMANCE.md](PERFORMANCE.md).
-- **API Stability Contract**: 0.x vs 1.0 stability guarantees — see [STABILITY.md](STABILITY.md).
-- **Architecture Decisions**: accepted decisions and rationale — see
- [ADR index](docs/adr/README.md).
-- **Compatibility Matrix**: supported Spring Boot / Java / Redisson lines — see [COMPATIBILITY.md](COMPATIBILITY.md).
-- **Security**: report privately — see [SECURITY.md](SECURITY.md).
+|---|:---:|:---:|:---:|:---:|
+| Multi-level local + remote cache | Yes | Local only | — | — |
+| Bloom filter | — | — | Manual | Yes |
+| TTL jitter | — | — | Manual | Yes |
+| Distributed breakdown lock | — | — | Manual | Yes |
+| Null-value caching | — | — | Manual | Yes |
+| Hot-key early refresh | — | — | Manual | Yes |
+| Declarative protection chain | Partial | — | — | Yes |
+| Broadcast invalidation | Yes | — | — | — |
+
+JetCache is a multi-level and broadcast-invalidation option. ResiCache is a
+Redisson-oriented protection chain. They solve different parts of the caching
+problem and are not presented as drop-in substitutes.
+
+## Limitations
+
+- **Pre-1.0 API**: minor releases may still change public contracts; review
+ [STABILITY.md](STABILITY.md) before pinning an extension point.
+- **Protection is opt-in**: the five `@RedisCacheable` protection attributes
+ default to `false`.
+- **Bloom membership is write-populated by default**: it does not scan existing
+ data, and an absent bit short-circuits the loader. Seed or maintain the public
+ `BloomIFilter` seam before enabling `useBloomFilter` for existing keys.
+- **Current artifact gap**: the current Boot 4 / Java 21 line is not yet
+ published to Maven Central; `0.0.2` is the historical Boot 3 / Java 17 line.
+- **Serializer migration required**: existing Spring-native serialized values
+ are not automatically compatible with ResiCache's envelope.
+- **Reactive caching is unsupported**: WebFlux `Mono` and `Flux` methods do not
+ use the blocking ResiCache interceptor.
+- **Async cached methods**: `@Async` methods are not supported for sync-lock and
+ Bloom-filter enhancements.
+- **Protection switches are startup-only**: changing `resi-cache.protection.*`
+ requires an application restart.
+- **Time-to-idle reads**: the native writer path intentionally does not refresh
+ TTL on read, avoiding write amplification.
+
+For the complete, tested boundary list, use
+[COMPATIBILITY.md](COMPATIBILITY.md).
+
+## Not in scope
+
+ResiCache deliberately does not implement capabilities that are better owned by
+specialized components:
+
+- **Circuit breaking and rate limiting** → [Resilience4j](https://resilience4j.readthedocs.io/)
+- **Multi-level local plus remote caching** → [Caffeine](https://github.com/ben-manes/caffeine) for the local tier
+- **Reactive caching** → not supported by the current blocking interceptor
+
+## Project layout
+
+```text
+ResiCache/
+├── src/main/java/io/github/davidhlp/spring/cache/redis/
+│ ├── annotation/ # Public ResiCache annotations
+│ ├── cache/ # Internal runtime, AOP, chain, operations, assembly
+│ ├── chain/ # Stable handler, operation, and result contracts
+│ ├── config/ # Auto-configuration and RedisProCacheProperties
+│ ├── protection/ # Stable BloomIFilter and LockManager seams
+│ └── serialization/ # Serializer and migration contracts
+├── src/test/java/ # Unit, contract, and integration tests
+├── resicache-bench/ # Standalone JMH benchmark module
+├── scripts/ci/ # CI and repository guards
+└── docs/adr/ # Accepted architecture decisions
+```
+
+## Development
+
+### Prerequisites
+
+- JDK 21
+- Maven 3.x, or the bundled Maven Wrapper
+- Docker for Testcontainers-backed Redis and Cluster verification
+
+### Verification commands
+
+```bash
+# No-Docker daily path
+./mvnw -Punit test -B
+
+# Full Redis/Testcontainers verification
+./mvnw clean verify -B
+
+# Separate style and naming gates
+./mvnw checkstyle:check -B
+bash scripts/ci/check-test-names.sh
+
+# Package without tests
+./mvnw clean package -DskipTests -B
+```
+
+`./mvnw clean verify -B` enforces the JaCoCo gate of 70% line coverage and 40%
+branch coverage. The full development and contribution workflow is documented
+in [CONTRIBUTING.md](CONTRIBUTING.md).
+
+## Project status and support
+
+- **Version**: `v0.0.2`; semantic-versioning guarantees are intentionally
+ limited before 1.0.
+- **Maintenance**: solo-maintained, non-SLA, best-effort support.
+- **Change history**: [CHANGELOG.md](CHANGELOG.md)
+- **Compatibility policy**: [COMPATIBILITY.md](COMPATIBILITY.md)
+- **API stability**: [STABILITY.md](STABILITY.md)
+- **Performance baseline**: [PERFORMANCE.md](PERFORMANCE.md)
+- **Architecture decisions**: [ADR index](docs/adr/README.md)
+- **Contributing**: [CONTRIBUTING.md](CONTRIBUTING.md)
+
+## Security
+
+Do not open public issues for suspected vulnerabilities. Follow the private
+reporting process in [SECURITY.md](SECURITY.md).
## License
diff --git a/README.zh-CN.md b/README.zh-CN.md
index 82b2418b..aee80432 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -1,68 +1,127 @@
# ResiCache
-> ⚠️ **本中文版可能滞后,请以 [英文 README](README.md) 为准(canonical / source of truth)。**
-
-**Spring Cache 的防护增强注解生态** —— 在 `@Cacheable` 之外,用 `@RedisCacheable` 一行注解为 Redis 缓存补齐防穿透 / 防击穿 / 防雪崩 / 热 key 早刷新能力,通过可编排的责任链注入防护,不重造 AOP。
+**面向 Redis 的 Spring Cache 防护增强。** ResiCache 通过可编排的责任链,
+为 Spring Cache 增加缓存穿透、缓存击穿、缓存雪崩和热点 key 提前刷新防护,
+同时保留 Spring Cache 作为应用侧的使用模型。
[](https://github.com/davidhlp/ResiCache/actions/workflows/ci.yml)
[](LICENSE)
-> **项目状态:早期(v0.0.2)· Non-SLA best-effort · 单人维护**
-> 生产采用前请务必先读 [⚠️ Known Limitations](README.md#known-limitations)。
-
-## 这是什么
-
-Spring Cache(`@Cacheable` / `@CachePut` / `@CacheEvict`)只解决"缓存",不解决"防护"——缓存穿透、击穿、雪崩、热 key 过期都要业务自己补。ResiCache 用一套 **`@RedisCacheable` 增强注解** + **可编排的责任链**,把这些防护变成声明式能力:
+[English](README.md) · [简体中文](README.zh-CN.md)
+
+> [!WARNING]
+> ResiCache 仍处于 1.0 之前(`v0.0.2`),不提供 SLA,目前由单人维护。
+> 当前 `main` 构建线目标为 Spring Boot 4.0 与 Java 21,但尚未发布匹配的
+> Maven Central 产物。正式采用前请先阅读[兼容性矩阵](COMPATIBILITY.md)和
+> [已知限制](#已知限制)。
+
+## 目录
+
+- [概览](#概览)
+- [功能特性](#功能特性)
+- [兼容性与要求](#兼容性与要求)
+- [快速开始](#快速开始)
+- [工作原理](#工作原理)
+- [配置](#配置)
+- [扩展点](#扩展点)
+- [运行时语义](#运行时语义)
+- [方案对比](#方案对比)
+- [已知限制](#已知限制)
+- [不在范围内](#不在范围内)
+- [项目结构](#项目结构)
+- [开发与验证](#开发与验证)
+- [项目状态与支持](#项目状态与支持)
+- [安全](#安全)
+- [许可证](#许可证)
+
+## 概览
+
+Spring Cache 提供了统一的缓存编程模型,但本身不负责缓存穿透、缓存击穿、
+缓存雪崩或热点 key 过期防护。ResiCache 在 Spring Cache 与 Redis 之上,
+将这些防护能力显式化、可组合化。
+
+ResiCache 的设计目标是:
+
+- **保持 Spring Cache 作为边界**:应用继续使用 `@EnableCaching`、缓存操作
+ 和 Spring 的缓存抽象。
+- **让防护可组合**:每种机制都是一条有明确顺序的类型化 Handler,而不是
+ 分散在业务服务中的独立 AOP 逻辑。
+- **关键边界默认失败关闭**:缺少分布式锁支持、不安全的序列化和非法配置都
+ 应当可观测,而不是静默地降低保证等级。
+- **保持职责聚焦**:ResiCache 不是多级缓存、熔断器、限流器或 Reactive 缓存
+ 框架的替代品。
+
+## 功能特性
+
+| 能力 | 说明 |
+|---|---|
+| 布隆过滤器 | 拦截已知不存在的 key,降低缓存穿透 |
+| 分布式锁 | 基于 Redisson,为缓存击穿提供并发加载协调 |
+| TTL 抖动 | 随机化过期时间,减少缓存雪崩 |
+| 空值缓存 | 显式开启后缓存负查询结果 |
+| 提前过期 | 在正常过期边界前刷新热点 key |
+| 可编排责任链 | 通过 `HandlerOrder` 与 `@HandlerPriority` 统一排列防护机制 |
+| 安全序列化 | 使用受控的反序列化白名单和内部 wire envelope |
+| Spring Cache 集成 | 复用 Spring Cache,而不是引入第二套 AOP 模型 |
+
+`@RedisCacheable` 上的五类防护属性默认全部为 `false`;请根据应用的风险
+模型显式开启需要的机制。
+
+## 兼容性与要求
+
+当前仓库只维护一条构建线:
+
+| 组件 | 当前 `main` 构建线 |
+|---|---:|
+| Java | 21 |
+| Spring Boot | 4.0.0 |
+| Spring Framework / Spring Cache | 7.x,由 Boot 提供 |
+| Spring Data Redis | 4.0.x |
+| Redis Server | 7.x |
+| Redisson | 3.50.0,可选 |
+| Caffeine | 3.1.8,供内部能力使用 |
+| Docker | Testcontainers 验证必需 |
+| Testcontainers | 1.20.6,仅用于测试 |
-- **与 Spring Cache 共存**:继承 `RedisCacheManager` / `CacheInterceptor`,不替换 `@EnableCaching`,不重造 AOP
-- **与 JetCache 的差异**:JetCache 主打**多级缓存**,ResiCache 主打**缓存防护纵深**——责任链上每个 handler 可插拔、可编排,这是 JetCache 做不到的
+完整版本矩阵和边界说明见 [COMPATIBILITY.md](COMPATIBILITY.md)。
-## 📋 功能特性
+### 已发布产物状态
-| 特性 | 说明 |
-|------|------|
-| **布隆过滤器** | 防缓存穿透,拦截不存在的 key |
-| **分布式锁** | 基于 Redisson,防缓存击穿(**需 Redisson 在 classpath**) |
-| **TTL 抖动** | 随机化 TTL,防缓存雪崩 |
-| **空值缓存** | 缓存 null,防穿透 |
-| **提前过期** | 异步提前刷新热 key,提升命中率 |
-| **可编排责任链** | handler 按优先级串接,支持自定义插队(差异化能力) |
-| **安全序列化** | 白名单反序列化,防 Jackson 多态类型攻击 |
+当前 Boot 4 / Java 21 构建线以源码为主,尚未发布到 Maven Central。
+`io.github.davidhlp:ResiCache:0.0.2` 是历史上的 Boot 3 / Java 17 产物,
+不要把它当作当前 `main` 构建线的依赖。
-> ResiCache **不提供** 熔断 / 限流 / 多级本地缓存 / Reactive 支持,见 [🚫 Not in Scope](#-not-in-scope)。
+如需试用当前构建线,请[从源码构建](#开发与验证),或使用明确声明兼容性
+构建线的正式 release。
-## 🏗️ 架构设计
+## 快速开始
-ResiCache 采用 **责任链模式** 实现缓存写入防护。处理器顺序由 `HandlerOrder` 枚举统一定义,通过 `@HandlerPriority` 绑定:
+### 1. 在本地构建并安装当前源码
-```
-┌─────────────────────────────────────────────────────────────┐
-│ CacheHandlerChain │
-├─────────────────────────────────────────────────────────────┤
-│ ① BloomFilter (100) ── 布隆过滤器,防缓存穿透 │
-│ ② SyncLock (200) ── 分布式锁,防缓存击穿 │
-│ ③ EarlyExpiration (250) ── 提前过期,热 key 保护 │
-│ ④ TTL (300) ── TTL 抖动,防缓存雪崩 │
-│ ⑤ NullValue (400) ── 空值缓存,防穿透 │
-│ ⑥ ActualCache (500) ── 实际 Redis 写入 │
-└─────────────────────────────────────────────────────────────┘
+```bash
+git clone https://github.com/davidhlp/ResiCache.git
+cd ResiCache
+./mvnw -Punit test -B
+./mvnw install -DskipTests -B
```
-每个 Handler 通过返回包含明确控制决策(`FlowControl.CONTINUE`、`SKIP_ALL`、`TERMINATE`)的 `HandlerResult` 来控制责任链调度;需要后置回填或异步通知的 Handler 重写 `requiresPostProcess` 与 `afterChainExecution` 钩子。自定义 Handler 只需实现 `CacheHandler` 接口并标注 `@HandlerPriority`。
+第一条命令是不依赖 Docker 的贡献者检查;第二条命令会把当前源码按 POM
+版本安装到本地 Maven 仓库,供本地消费者试用,不会发布产物。不要把这个本地
+构建与 Maven Central 上历史版本的 `0.0.2` 混淆。
-## 🚀 快速开始
-
-### 1. 添加依赖
+对于本地消费者应用,将当前检出版本的坐标加入该应用的 `pom.xml`:
```xml
io.github.davidhlp
ResiCache
- 0.0.2
+ 0.0.2
```
-> Maven Central 上的 `0.0.2` 是**早期 Boot 3 / Java 17 构建**(2026-09-05 已核实:Central 全部版本均属旧线)。当前 Boot 4 / Java 21 构建线尚未发布产物,上述坐标为规划坐标。
+该依赖从本地 Maven 仓库解析。下面的配置和应用示例都应在这个消费者应用
+中执行,而不是在 ResiCache 源码检出目录中执行。完整的 Redis 与 Redis
+Cluster 验证命令见[开发与验证](#开发与验证)。
### 2. 配置 Redis
@@ -72,11 +131,30 @@ spring:
redis:
host: localhost
port: 6379
+ # timeout: 2s
+resi-cache:
+ redis:
+ mode: single
+ host: localhost
+ port: 6379
+ database: 0
+ tls-enabled: false
```
-> ResiCache 通过 Spring Boot 自动装配生效(入口 `RedisCacheAutoConfiguration`,见 `META-INF/spring/...AutoConfiguration.imports`),无需额外 `@EnableXxx`。
+`spring.data.redis.*` 与 `resi-cache.redis.*` 配置的是相互独立的客户端。
+`spring.data.redis.*` 配置 ResiCache 缓存 I/O 使用的 Spring Data Redis
+连接工厂。`resi-cache.redis.*` 配置分布式锁和同步能力使用的 Redisson
+部署;`resi-cache.redisson.*` 控制其连接池、超时和重试设置。`resi-cache.redis.*`
+的默认 host、port、database 分别为 `localhost`、`6379`、`0`,因此省略
+端点字段不会继承 `spring.data.redis.*`。在 single 模式下,如果
+`resi-cache.redis.password` 未设置,Redisson 可能回退使用 Spring Data Redis
+密码。使用 `sync=true` 时,对于 single、cluster 或 sentinel 部署,都必须在
+两个命名空间中显式配置匹配的端点或拓扑;不要依赖一个命名空间配置另一个。
-### 3. 启用缓存
+ResiCache 通过 Spring Boot 自动配置入口 `RedisCacheAutoConfiguration` 被发现。
+它不会替应用添加 `@EnableCaching`;是否启用 Spring Cache 仍由应用负责。
+
+### 3. 启用 Spring Cache
```java
@SpringBootApplication
@@ -88,67 +166,130 @@ public class Application {
}
```
-### 4. 使用注解
-
-**推荐:`@RedisCacheable`(防护入口)**
+### 4. 使用防护注解
```java
@Service
public class UserService {
- @RedisCacheable(value = "users", key = "#id",
- useBloomFilter = true, // 布隆过滤防穿透
- cacheNullValues = true, // 空值缓存
- randomTtl = true, // TTL 抖动防雪崩
- variance = 0.2, // 抖动幅度 ±20%
- enableEarlyExpiration = true) // 热 key 提前刷新
+
+ @RedisCacheable(
+ value = "users",
+ key = "#id",
+ cacheNullValues = true,
+ randomTtl = true,
+ variance = 0.2,
+ enableEarlyExpiration = true)
public User getUserById(Long id) {
return userRepository.findById(id);
}
}
```
-**兼容:`@Cacheable`(不增强防护)**
+防护属性刻意要求显式开启。普通 `@Cacheable` 仍可与 Spring Cache 共存,
+但在默认的 `nativeAnnotationMode=SELECTIVE` 模式下不会获得 ResiCache 防护:
```java
-@Cacheable(value = "users", key = "#id") // 可共存,但不获得防护
-public User getUserById(Long id) { ... }
+@Cacheable(value = "users", key = "#id")
+public User getUserById(Long id) {
+ // 走 Spring 原生缓存路径;需要防护时请使用 @RedisCacheable。
+}
+```
+
+## 工作原理
+
+### Handler 责任链
+
+防护能力以责任链方式组装。执行顺序由 `HandlerOrder` 枚举统一定义,并通过
+`@HandlerPriority` 绑定:
+
+```text
+┌─────────────────────────────────────────────────────────────┐
+│ CacheHandlerChain │
+├─────────────────────────────────────────────────────────────┤
+│ ① BloomFilter (100) ── 布隆过滤器,防缓存穿透 │
+│ ② SyncLock (200) ── 分布式锁,防缓存击穿 │
+│ ③ EarlyExpiration (250) ── 提前过期,热点 key 保护 │
+│ ④ TTL (300) ── TTL 抖动,防缓存雪崩 │
+│ ⑤ NullValue (400) ── 空值缓存,防缓存穿透 │
+│ ⑥ ActualCache (500) ── 实际 Redis 写入 │
+└─────────────────────────────────────────────────────────────┘
```
-> `@Cacheable` 可与 ResiCache 共存,但**不获得防护**——防护属性(`useBloomFilter` / `randomTtl` / ...)仅在 `@RedisCacheable` 上。要防护请用 `@RedisCacheable`。
+每个 Handler 返回带有明确 `FlowControl` 决策(`CONTINUE`、`SKIP_ALL` 或
+`TERMINATE`)的类型化 `HandlerResult`。Handler 可以通过
+`requiresPostProcess` 与 `afterChainExecution` 参与后置处理;链引擎负责
+推进主链,并隔离后置处理失败。
+
+内置 Handler 由库的自动配置注册,只扫描库内部 runtime 包。应用自定义
+Handler 必须由应用自己的组件扫描发现,或作为应用 Bean 提供;库的内部扫描
+不会扫描宿主应用包。
+
+### 防护边界
-## ⚙️ 配置选项
+- **缓存穿透**:Bloom 缺少 membership bit 时会被视为确定 miss。默认实现只在缓存
+ 写入成功后回填,不会扫描或从数据源自动重建。对于过滤器中尚未存在的缓存 key,
+ 启用前必须通过公共 `BloomIFilter` seam 预填充或维护 membership set。
+- **缓存击穿**:当 Redisson 或其他 `LockManager` 可用时,sync Handler 通过
+ 分布式锁协调并发加载。
+- **缓存雪崩**:`randomTtl=true` 时,TTL 抖动会分散过期时间。
+- **热点 key 过期**:显式开启后,提前过期机制会在正常过期边界前安排刷新。
+- **负查询**:显式开启后,空值缓存可以保留不存在数据的结果。
-多数配置绑定到 `RedisProCacheProperties`;四个 `resi-cache.bloom.*` 实现参数由自动配置显式绑定,并由 additional metadata 描述。
+## 配置
-### 总开关(当前未发布契约)
+大多数配置使用 `resi-cache.*` 前缀,并绑定到
+`RedisProCacheProperties`。配置在启动阶段解析;修改防护开关需要重启应用。
+
+### 启用与防护开关
```yaml
resi-cache:
- enabled: true # 主开关;false 完全禁用 ResiCache
+ enabled: true
+ native-annotation-mode: SELECTIVE # FULL | NONE | SELECTIVE
protection:
- enabled: true # false 跳过 bloom/lock/early-exp/null-value;TTL 保留
- bloom-filter-enabled: null # 机制级覆盖,启动时静态解析:
- sync-lock-enabled: null # null 继承总开关;false 只关闭该机制;总开关为
- early-expiration-enabled: null # false 时分项 true 不能重新启用。修改配置需重启。
+ enabled: true
+ bloom-filter-enabled: null # null 继承 protection.enabled
+ sync-lock-enabled: null
+ early-expiration-enabled: null
null-value-enabled: null
```
-防护开关**仅启动时生效**:责任链单例在首次构建时缓存;分项 `true` 无法覆盖总开关
-`false`;修改防护配置需重启应用。TTL 与 ActualCache 始终保留。
+`resi-cache.enabled=false` 会关闭库的自动配置。`protection.enabled` 会关闭
+布隆、同步锁、提前过期和空值防护,但保留基础 TTL 与实际缓存 Handler。
+当总防护开关为 `false` 时,机制级 `true` 不能重新开启对应机制。
+
+`native-annotation-mode` 控制 Spring 原生缓存注解:
+
+- `SELECTIVE`(默认):没有 ResiCache 注解的纯原生方法保留在 Spring
+ 原生路径中,不会被 ResiCache operation source 转换。存在 ResiCache 注解
+ 时,匹配的原生 operation 会被 ResiCache 转换器跳过(这是内部转换去重),
+ 但显式混用注解的方法仍可能同时被 ResiCache Advisor 和 Spring 原生缓存
+ Advisor 看到;同一方法不要混用注解,除非已经验证实际结果。
+- `FULL`:转换所有受支持的 Spring 原生缓存注解,包括只有原生注解的方法。
+ 当 `@EnableCaching` 或其他 Spring 原生 Advisor 生效时,这些方法也可能被
+ 两个 Advisor 处理;只有在明确需要并测试了这种重复拦截或执行时才应启用。
+- `NONE`:在 ResiCache operation source 中忽略 Spring 原生缓存注解。
### 全局配置
```yaml
resi-cache:
- default-ttl: 30m # 默认 TTL
- key-prefix: "" # 全局 key 前缀
- transaction-aware: false # 事务感知缓存
+ default-ttl: 30m
+ key-prefix: ""
+ transaction-aware: false
```
### 指标与健康检查
-缓存指标需要显式设置 `resi-cache.metrics.enabled=true`(默认关闭),并且应用
-提供 `MeterRegistry`。健康检查使用同一显式属性,并需要 Actuator。
+指标默认选择性关闭,需要同时显式设置属性并提供可用的 `MeterRegistry`:
+
+```yaml
+resi-cache:
+ metrics:
+ enabled: true
+```
+
+Redis cache health indicator 使用同一个显式属性,并且需要 Spring Boot Actuator。
### 布隆过滤器
@@ -160,6 +301,7 @@ resi-cache:
hash-functions: 3
hash-cache-size: 10000
```
+这些配置绑定在 `resi-cache.bloom.*` 前缀下。
### 分布式锁
@@ -169,79 +311,70 @@ resi-cache:
timeout: 3000
unit: MILLISECONDS
prefix: "cache:lock:"
- local-only: false # true = 无 Redisson 时显式单 JVM 降级(否则 fail-fast)
+ local-only: false
```
-### 提前过期(热 key 保护)
+`sync=true` 需要 Redisson 等分布式锁实现。当 `local-only=false`(默认)时,
+缺少分布式支持会失败关闭,而不是静默声称具备多实例防护。只有在单 JVM 降级
+明确可接受时,才应设为 `local-only=true`。
+
+### 提前过期
```yaml
resi-cache:
protection:
- early-expiration-enabled: true # 可选的机制级覆盖(仅启动时生效;null 继承总开关)
+ early-expiration-enabled: true
early-expiration:
- pool-size: 2 # 核心线程数
- max-pool-size: 10 # 最大线程数
- queue-capacity: 100 # 队列容量
+ pool-size: 2
+ max-pool-size: 10
+ queue-capacity: 100
```
-### Redis 部署
+### Redis 部署方式
```yaml
resi-cache:
redis:
- mode: single # single | cluster | sentinel
+ mode: single # single | cluster | sentinel
host: localhost
port: 6379
database: 0
tls-enabled: false
# cluster-nodes: [host1:6379, host2:6379]
# sentinel-master: mymaster
- # sentinel-nodes: [host1:26379, host2:26379]
+ # sentinel-nodes: [host1:26379]
```
+部署配置会在绑定阶段校验与模式相关的字段。`resi-cache.redis.*` 是
+Redisson 部署配置路径;`spring.data.redis.*` 配置缓存 I/O 使用的独立连接
+工厂。`resi-cache.redis.host`、`port`、`database` 的默认值分别为
+`localhost`、`6379`、`0`,因此省略端点字段不会采用 Spring Data Redis 的值。
+在 single 模式下,如果 `resi-cache.redis.password` 未设置,Redisson 可能
+回退使用 Spring Data Redis 密码。使用 `sync=true` 时,对于 single、cluster
+或 sentinel 部署,都必须在两个命名空间中显式配置匹配的端点或拓扑;不要
+依赖一个命名空间配置另一个。`resi-cache.redisson.*` 控制 Redisson 连接池、
+超时和重试设置。生产环境凭据应交由应用的密钥管理系统处理,不要写入提交的
+README 示例。
+
### 序列化安全
```yaml
resi-cache:
serializer:
- type-property: "@class" # Jackson 类型标签
- polymorphic-typing-enabled: false # 默认关闭,更安全
- fail-on-unknown-type: true # 未知类型即失败
- allowed-package-prefixes: # 反序列化白名单
- - "io.github.davidhlp."
- - "com.example." # ← 务必加上你自己的业务包名
+ type-property: "@class"
+ polymorphic-typing-enabled: false
+ fail-on-unknown-type: true
+ allowed-package-prefixes:
+ - "io.github.davidhlp"
+ - "com.example.*"
```
-> ⚠️ **白名单默认仅含 `io.github.davidhlp`**(字面前缀,沿用现有 `startsWith` 语义)。如需 dot 边界保护,请配置 `io.github.davidhlp.*`;缓存自定义业务类型(如 `com.example.User`)时,**必须**显式添加你的包名,否则反序列化会抛异常。
->
-> **通配形式(当前未发布)**:以 `.*` 结尾的前缀被解释为通配符——匹配直接类(`com.example.Foo`)、所有子包内的类(`com.example.sub.Bar`、`com.example.foo.bar.baz.Qux` …),并以 dot 边界保护(`com.example.*` **不会**误匹配 `com.exampleX.Foo`)。当你想允许整个包子树、无需逐子包列出时使用。
->
-> ```yaml
-> resi-cache:
-> serializer:
-> allowed-package-prefixes:
-> - "io.github.davidhlp."
-> - "com.example.*" # 一行搞定整棵 com.example 子树
-> ```
-
-### 注解级属性(`@RedisCacheable`)
-
-| 属性 | 默认值 | 说明 |
-|------|--------|------|
-| `ttl` | 60 | 缓存 TTL(秒) |
-| `cacheNullValues` | false | 是否缓存 null |
-| `useBloomFilter` | false | 启用布隆过滤 |
-| `expectedInsertions` | 100000 | 布隆预期插入量 |
-| `falseProbability` | 0.01 | 布隆误判率 |
-| `randomTtl` | false | 启用 TTL 抖动 |
-| `variance` | 0.2 | TTL 抖动幅度 |
-| `enableEarlyExpiration` | false | 启用提前过期 |
-| `earlyExpirationThreshold` | 0.3 | 提前过期阈值(TTL 剩余比例) |
-| `sync` / `syncTimeout` | false / 10 | 同步等待与超时 |
-
-> 注:5 大防护属性**默认全 `false`**——须在 `@RedisCacheable` 上逐个显式开启。`sync=true`(防击穿)依赖 Redisson 在 classpath,**缺失时 fail-fast**(拒绝静默降级为单 JVM 锁——跨实例无效);确需单实例/测试降级,设 `resi-cache.sync-lock.local-only=true`。
-
-### 每缓存覆盖(`caches.`)
+默认白名单是字面前缀 `io.github.davidhlp`,应用自己的业务包必须显式加入。
+如需 dot 边界保护的子树匹配,请使用以 `.*` 结尾的前缀;例如
+`com.example.*` 会匹配 `com.example.User` 与 `com.example.orders.Order`,但不会
+匹配 `com.exampleX.User`。
+
+### 按缓存覆盖配置
```yaml
resi-cache:
@@ -252,115 +385,177 @@ resi-cache:
key-prefix: "users:"
```
-## 📖 工作原理
+### `@RedisCacheable` 属性
-### 缓存穿透防护
+| 属性 | 默认值 | 作用 |
+|---|---:|---|
+| `ttl` | `60` | 缓存 TTL,单位为秒 |
+| `cacheNullValues` | `false` | 是否缓存 `null` 结果 |
+| `useBloomFilter` | `false` | 是否启用布隆过滤防护 |
+| `expectedInsertions` | `100000` | 布隆过滤器预期插入量 |
+| `falseProbability` | `0.01` | 布隆过滤器误判率目标 |
+| `randomTtl` | `false` | 是否启用 TTL 抖动 |
+| `variance` | `0.2` | TTL 抖动幅度 |
+| `enableEarlyExpiration` | `false` | 是否启用热点 key 提前刷新 |
+| `earlyExpirationThreshold` | `0.3` | 触发刷新的剩余 TTL 比例 |
+| `sync` / `syncTimeout` | `false` / `10` | 是否启用同步加载及等待超时 |
-布隆过滤器在缓存层之前拦截不存在的数据请求:
+## 扩展点
-```
-请求 ──→ BloomFilter ──→ 存在?──→ 是 ──→ 继续执行
- │
- └──→ 否 ──→ 直接返回 null(不查缓存)
-```
+### 注解族
-### 缓存击穿防护
+公共注解族与 Spring Cache 操作相对应:
-分布式锁确保同一时刻只有一个请求去加载数据:
+- `@RedisCacheable`:读穿缓存以及防护属性。
+- `@RedisCachePut`:显式写入缓存。
+- `@RedisCacheEvict`:移除缓存。
+- `@RedisCaching`:在一个方法或类型上组合多个 ResiCache 操作。
-```
-请求A ──→ 获取锁 ──→ 查 DB ──→ 写入缓存 ──→ 释放锁
-请求B ──→ 获取锁 ──→ 已存在,直接从缓存获取
-```
+`@RedisCaching` 可以在类型级别暴露操作,但其中组合注解的保护策略字段按
+方法级别求值。仅有类型级声明不会把这些字段应用到未添加方法级注解的方法;
+如果某个方法需要该策略,请在方法级别重复相关的 `@RedisCacheable`、
+`@RedisCachePut` 或 `@RedisCacheEvict`。
-### 缓存雪崩防护
+### 自定义 Handler
-TTL 随机化避免大量缓存同时过期:
+自定义 Handler 实现公共 `CacheHandler` 契约,并使用带有 `HandlerOrder` 值的
+`@HandlerPriority`。它必须被应用自己的组件扫描发现,或作为应用 Bean 提供。
+Handler 返回类型化 `HandlerResult`,也可以参与后置处理,而不需要自行维护一个
+指向下一个节点的链表。
-```
-设置 TTL = baseTtl ± variance × baseTtl (randomTtl=true 时生效)
-```
+只有文档明确声明的公共 seam 才适合替换。依赖公共类型或修改 Handler 行为前,
+请先阅读 [STABILITY.md](STABILITY.md)。
-## 🆚 与 JetCache / Caffeine / 裸 Redisson 对比
+## 运行时语义
-在 Redis 之上做缓存,常见有四个选项:JetCache、Caffeine、裸 Redisson、
-ResiCache。本项目定位:**ResiCache for Redisson — Redisson 忘了做的那条
-可声明缓存防护链**。
+### 缓存 I/O 失败行为
-| 能力 | JetCache | Caffeine | 裸 Redisson | **ResiCache** |
-|------|:--------:|:--------:|:-----------:|:-------------:|
-| 多级缓存(本地 + 远程) | ✅ | 仅本地 | — | — |
-| 布隆过滤器(防穿透) | — | — | 手写 | ✅ |
-| TTL 抖动(防雪崩) | — | — | 手写 | ✅ |
-| 分布式击穿锁(防击穿) | — | — | 手写 | ✅ |
-| null 值缓存 | — | — | 手写 | ✅ |
-| 热点 Key 提前刷新 | — | — | 手写 | ✅ |
-| 声明式 `@注解` 责任链 | 部分 | — | — | ✅ |
-| 跨实例广播失效 | ✅ | — | — | — |
-
-一句话结论:**JetCache 缺的那 3 项防护,以 Redisson-native 责任链补齐** —
-布隆过滤器(防穿透)、TTL 抖动(防雪崩)、分布式击穿锁(防击穿)。
-ResiCache 是补齐这 3 项空白的 Redisson 搭档;JetCache 主打多级缓存与
-跨实例广播失效。两者**作用域互补,不是直接替代**。
-
-## 📦 项目结构
+| 操作 | 行为 |
+|---|---|
+| GET | 降级为 cache miss,并记录内部失败 |
+| PUT / PUT_IF_ABSENT / CLEAN | 抛出保留原始 cause 的类型化运行时异常 |
+| REMOVE | 可观测的 best-effort 移除;移除失败不向上抛出 |
+| `get(key, loader)` 且 loader 成功 | 即使写回失败也返回 loader 值;写回失败日志不包含原始 key |
+| `get(key, loader)` 且 loader 失败 | 抛出 Spring 的 `Cache.ValueRetrievalException` |
-```
-src/main/java/io/github/davidhlp/spring/cache/redis/
-├── annotation/ # @RedisCacheable, @RedisCacheEvict, @RedisCachePut, @RedisCaching
-├── cache/ # package-private runtime:AOP、责任链、防护、操作、序列化与装配
-├── chain/ # 稳定 CacheHandler/Operation/Result 契约与决策视图
-├── config/ # 自动配置、RedisProCacheProperties 与 metrics 入口
-├── protection/ # 稳定 BloomIFilter、LockManager、EarlyExpirationMode 契约
-└── serialization/ # SerializationException、迁移 operator 契约与 wire envelope
+`@CacheEvict(allEntries=true)` / CLEAN 是 best-effort、非原子操作,使用 SCAN 游标
+和批量删除。Bloom bit 是独立维护的 membership set,默认由成功写入回填;不会从
+数据源自动重建。清理缓存条目不会删除这些 bit。
+
+### 序列化迁移
+
+ResiCache 将值存储在内部 `{version, payload}` envelope 中,与 Spring 的
+`GenericJackson2JsonRedisSerializer` 或 `JdkSerializer` 不兼容。已有应用不应
+假定直接切换序列化器是安全的,应采用有边界的
+shadow-read → dual-write → cutover 迁移流程。迁移指引见
+[COMPATIBILITY.md](COMPATIBILITY.md)。
+
+## 方案对比
+
+ResiCache 刻意比通用缓存框架更聚焦:
+
+| 能力 | JetCache | Caffeine | 裸 Redisson | **ResiCache** |
+|---|:---:|:---:|:---:|:---:|
+| 多级本地 + 远端缓存 | 有 | 仅本地 | — | — |
+| 布隆过滤器 | — | — | 手写 | 有 |
+| TTL 抖动 | — | — | 手写 | 有 |
+| 分布式击穿锁 | — | — | 手写 | 有 |
+| 空值缓存 | — | — | 手写 | 有 |
+| 热点 key 提前刷新 | — | — | 手写 | 有 |
+| 声明式防护责任链 | 部分 | — | — | 有 |
+| 跨实例广播失效 | 有 | — | — | — |
+
+JetCache 侧重多级缓存和跨实例广播失效;ResiCache 侧重面向 Redisson 的防护
+责任链。两者解决缓存问题的不同部分,不是互相替代的同类方案。
+
+## 已知限制
+
+- **仍处于 1.0 之前**:小版本仍可能改变公共契约;依赖扩展点前请阅读
+ [STABILITY.md](STABILITY.md)。
+- **防护默认关闭**:`@RedisCacheable` 的五类防护属性默认都是 `false`。
+- **Bloom membership 默认由写入回填**:不会扫描已有数据,缺少 bit 会短路 loader。
+ 对已有 key 启用 `useBloomFilter` 前,请先通过公共 `BloomIFilter` seam 预填充或维护。
+- **当前构建线尚无产物**:Boot 4 / Java 21 构建线尚未发布到 Maven Central;
+ `0.0.2` 是历史上的 Boot 3 / Java 17 构建线。
+- **需要序列化迁移**:已有 Spring 原生序列化值不会自动兼容 ResiCache envelope。
+- **不支持 Reactive 缓存**:WebFlux 的 `Mono` 与 `Flux` 方法不会进入阻塞式
+ ResiCache interceptor。
+- **异步缓存方法有限制**:`@Async` 方法不支持同步锁和布隆过滤增强。
+- **防护开关只在启动时解析**:修改 `resi-cache.protection.*` 后必须重启应用。
+- **Time-to-idle 读取**:底层 writer 读取路径刻意不在读时刷新 TTL,以避免额外
+ 写放大。
+
+完整、经过测试的边界列表请以 [COMPATIBILITY.md](COMPATIBILITY.md) 为准。
+
+## 不在范围内
+
+ResiCache 刻意不实现以下更适合由专业组件负责的能力:
+
+- **熔断与限流** → [Resilience4j](https://resilience4j.readthedocs.io/)
+- **多级本地 + 远端缓存** → 使用 [Caffeine](https://github.com/ben-manes/caffeine) 作为本地层
+- **Reactive 缓存** → 当前阻塞式 interceptor 不支持
+
+## 项目结构
+
+```text
+ResiCache/
+├── src/main/java/io/github/davidhlp/spring/cache/redis/
+│ ├── annotation/ # 公共 ResiCache 注解
+│ ├── cache/ # 内部 runtime、AOP、责任链、操作与装配
+│ ├── chain/ # 稳定的 Handler、operation、result 契约
+│ ├── config/ # 自动配置与 RedisProCacheProperties
+│ ├── protection/ # 稳定的 BloomIFilter 与 LockManager seam
+│ └── serialization/ # 序列化与迁移契约
+├── src/test/java/ # 单元、契约与集成测试
+├── resicache-bench/ # 独立 JMH 基准模块
+├── scripts/ci/ # CI 与仓库守卫脚本
+└── docs/adr/ # 已接受的架构决策
```
-## ⚠️ Known Limitations / 已知限制
+## 开发与验证
-v0.0.x 当前已知限制:
+### 前置条件
-- **防护默认全关**:`@RedisCacheable` 的 5 大防护属性默认 `false`,须逐个显式开启
-- **序列化信封与 Spring 原生不兼容**:ResiCache 用 `{version, payload}` 信封序列化,与 Spring 默认 `GenericJackson2JsonRedisSerializer` / `JdkSerializer` 不兼容——**存量项目接入需迁移**,否则全量缓存失效
-- **序列化白名单默认锁作者包名**:`allowed-package-prefixes` 默认仅 `io.github.davidhlp.`,自定义类型须显式配置(见上文 [序列化安全](#序列化安全))
-- **双 Advisor 风险已消除**:`nativeAnnotationMode` 默认 `SELECTIVE`——纯 `@Cacheable` 完全走 Spring 原生、不被 ResiCache 接管。需要 FULL 兼容(接管 `@Cacheable`)可显式 `resi-cache.native-annotation-mode=FULL`
-- **不支持 Reactive**(WebFlux / `Mono` / `Flux`):`RedisCacheInterceptor` 是阻塞式,Reactive 方法不触发缓存
-- **缓存 I/O 失败语义**:GET 降级为 miss 并记录日志;PUT、
- PUT_IF_ABSENT、CLEAN 抛出带原始 cause 的类型化运行时异常;
- REMOVE 为可观测 best-effort,不抛异常。**read-through(`get(key, loader)`)可用性优先**:
- loader 成功值必定返回——写回失败仅记录(脱敏)日志、不覆盖该值;
- loader 失败仍以 Spring `Cache.ValueRetrievalException` 呈现。
-- **`@CacheEvict(allEntries=true)`(CLEAN)是 best-effort、非原子**:与 Spring 原生 `RedisCache.clear`/`DefaultRedisCacheWriter.clean` 一致,用 SCAN 游标 + 批量 UNLINK/DEL,CLEAN 期间新写入的 key 可能被遗漏,大 key 集时缓存短暂处于半删状态。刻意不用 Lua/MULTI 原子化(Redis 单线程 O(keyspace) 阻塞、Cluster cross-slot)。Bloom 表示数据源可能存在;CLEAN 只清缓存、保留旧 bits,因此只允许 false-positive,不会制造阻止 loader 的 false-negative。
+- JDK 21
+- Maven 3.x,或仓库自带的 Maven Wrapper
+- Docker:运行 Testcontainers Redis 与 Cluster 验证时必需
-## 🚫 Not in Scope
+### 验证命令
-ResiCache **刻意不做**以下能力,避免过度膨胀——请用专业工具配合:
+```bash
+# 不依赖 Docker 的日常路径
+./mvnw -Punit test -B
-- **熔断 / 限流** → 配 [Resilience4j](https://resilience4j.readthedocs.io/) 保护下游
-- **多级本地 + 远端缓存** → 配 [Caffeine](https://github.com/ben-manes/caffeine) 做本地层
-- **Reactive 缓存**(WebFlux)→ 不支持
+# 完整 Redis/Testcontainers 验证
+./mvnw clean verify -B
-## 🔧 依赖版本
+# 独立的代码风格与测试命名检查
+./mvnw checkstyle:check -B
+bash scripts/ci/check-test-names.sh
-| 依赖 | 版本 |
-|------|------|
-| Spring Boot | 4.0.0(parent) |
-| Java | 21 |
-| Redisson | 3.50.0(optional) |
-| Caffeine | 3.1.8 |
-| Testcontainers | 1.20.6 |
+# 跳过测试打包
+./mvnw clean package -DskipTests -B
+```
+
+`./mvnw clean verify -B` 强制 JaCoCo 覆盖率门槛:行覆盖率 70%,分支覆盖率
+40%。完整开发与贡献流程见 [CONTRIBUTING.md](CONTRIBUTING.md)。
+
+## 项目状态与支持
-完整兼容矩阵见 [COMPATIBILITY.md](COMPATIBILITY.md)。
+- **版本**:`v0.0.2`;1.0 之前只提供有限的语义化版本保证。
+- **维护**:单人维护、无 SLA、尽力支持。
+- **变更历史**:[CHANGELOG.md](CHANGELOG.md)
+- **兼容性策略**:[COMPATIBILITY.md](COMPATIBILITY.md)
+- **API 稳定性**:[STABILITY.md](STABILITY.md)
+- **性能基线**:[PERFORMANCE.md](PERFORMANCE.md)
+- **架构决策**:[ADR 索引](docs/adr/README.md)
+- **贡献指南**:[CONTRIBUTING.md](CONTRIBUTING.md)
-## 项目状态与维护
+## 安全
-- **版本**:v0.0.2 — 语义化版本 < 1.0,API 可能在 minor 版本变更,breaking 项在 [CHANGELOG.md](CHANGELOG.md) 标 ⚠️
-- **维护**:单人维护([DavidHLP](https://github.com/davidhlp)),**Non-SLA best-effort**——不承诺响应时间,但积极修复 issue
-- **贡献指南**:欢迎 PR,流程见 [CONTRIBUTING.md](CONTRIBUTING.md)
-- **性能实测基准**:JMH 实测数据与 SLO 达标表见 [PERFORMANCE.md](PERFORMANCE.md)
-- **稳定性契约**:0.x 周期兼容与 1.0 毕业指标见 [STABILITY.md](STABILITY.md)
-- **兼容性矩阵**:支持的 Spring Boot / Java / Redisson 版本见 [COMPATIBILITY.md](COMPATIBILITY.md)
-- **安全策略**:漏洞私有报告流程见 [SECURITY.md](SECURITY.md)
+疑似漏洞不要直接创建公开 issue,请按照 [SECURITY.md](SECURITY.md) 中的私有
+报告流程提交。
-## 📄 License
+## 许可证
[MIT License](LICENSE) © 2026 DavidHLP