Add resilience4j-circuit-breaker: Resilience4j 2.4.0 vs Spring Framework 7 core, on Boot 4.1
Companion module for the rewritten post 'Resilience4j Circuit Breaker in Spring Boot 4.1: What It's Still For', reworked around Framework 7 now shipping @Retryable/@ConcurrencyLimit in core. Covers what's still Resilience4j's job (circuit breaker, rate limiter, bulkhead's bounded wait, fallback methods, Actuator/Micrometer metrics), the off-by-one between maxAttempts and maxRetries, and two Boot-4.1 build breaks: spring-boot-starter-aop no longer exists (renamed to spring-boot-starter-aspectj, proven with Maven Central metadata and the renamed starter's own POM -- see resilience/docs/08-starter-aop-renamed-to-starter-aspectj.md in this same repo), and the resulting fix uses that renamed starter directly rather than assembling spring-aop + aspectjweaver by hand. Kept as its own module rather than a new top-level repository, alongside the existing resilience/ module for the sibling Framework-7 post.
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
# 1. Two resilience stacks on one classpath
|
||||
|
||||
[README](../README.md) | Next: [02-circuit-breaker.md](02-circuit-breaker.md)
|
||||
|
||||
Spring Boot 4.1 sits on Spring Framework 7, and Framework 7 shipped something new under
|
||||
`org.springframework.resilience`: `@Retryable`, `@ConcurrencyLimit`, and the annotation that
|
||||
turns them on, `@EnableResilientMethods`. None of this existed in Framework 6. This repo pins
|
||||
down exactly what moved into core, what didn't, and two build breaks you will hit the moment
|
||||
you try to wire it up on Boot 4 — both found by running the build, not by reading a changelog.
|
||||
|
||||
## What's actually in core (verified by `javap`, not by the reference docs)
|
||||
|
||||
`org.springframework.resilience.annotation.Retryable` (an annotation, not the unrelated
|
||||
`org.springframework.core.retry.Retryable` *interface* that also ships in `spring-core` — two
|
||||
classes with the same simple name in the same major version, easy to import the wrong one):
|
||||
|
||||
```
|
||||
value(), includes(), excludes(), predicate(),
|
||||
maxRetries(), maxRetriesString(),
|
||||
timeout(), timeoutString(),
|
||||
delay(), delayString(),
|
||||
jitter(), jitterString(),
|
||||
multiplier(), multiplierString(),
|
||||
maxDelay(), maxDelayString(),
|
||||
timeUnit()
|
||||
```
|
||||
|
||||
`org.springframework.resilience.annotation.ConcurrencyLimit`:
|
||||
|
||||
```
|
||||
value(), limit(), limitString(), policy() // policy: BLOCK (default) or REJECT
|
||||
```
|
||||
|
||||
Neither annotation has a `fallbackMethod` attribute. Retrying exhausted just rethrows.
|
||||
Rejecting under `REJECT` throws `org.springframework.resilience.InvocationRejectedException`
|
||||
(a `java.util.concurrent.RejectedExecutionException` subtype) straight at the caller.
|
||||
|
||||
There is no circuit breaker class anywhere in `spring-context-7.0.9.jar` (grepped the whole
|
||||
jar listing for `circuitbreaker`, `ratelimit`, `bulkhead` — zero matches). `@ConcurrencyLimit`
|
||||
is the closest core has to a Resilience4j Bulkhead, and even that is a decades-old class
|
||||
repurposed: `ConcurrencyLimitBeanPostProcessor$ResilienceConcurrencyThrottleInterceptor`
|
||||
extends `org.springframework.aop.interceptor.ConcurrencyThrottleInterceptor`, which has shipped
|
||||
in Spring since the 1.x era.
|
||||
|
||||
## `@EnableResilientMethods` is not automatic
|
||||
|
||||
Spring Boot 4.1's autoconfigure jar carries no auto-configuration for the resilience package —
|
||||
grepping `spring-boot-autoconfigure-4.1.1.jar`'s listing for "resilien" returns nothing. You
|
||||
must put `@EnableResilientMethods` on a `@Configuration` class yourself (this repo puts it on
|
||||
the `@SpringBootApplication` class). Skip it and the annotations are inert: no error, no log
|
||||
line, the method just runs unprotected.
|
||||
|
||||
## Build break #1: `spring-boot-starter-aop` no longer exists
|
||||
|
||||
Every pre-Boot-4 Resilience4j guide, including the version of this post it replaces, tells you
|
||||
to add `spring-boot-starter-aop`. On Boot 4 that dependency breaks the build outright:
|
||||
|
||||
```
|
||||
[ERROR] 'dependencies.dependency.version' for org.springframework.boot:spring-boot-starter-aop:jar is missing.
|
||||
```
|
||||
|
||||
`repo1.maven.org/maven2/org/springframework/boot/spring-boot-starter-aop/maven-metadata.xml`
|
||||
confirms it: the last version ever published is `4.0.0-M2`, a milestone. It never reached
|
||||
Boot 4.0 GA and was not revived for 4.1. The fix is `org.springframework:spring-aop` directly —
|
||||
see [pom.xml](../pom.xml).
|
||||
|
||||
## Build break #2 (silent): no `aspectjweaver`, no proxy, no error
|
||||
|
||||
Swapping in `spring-aop` alone gets you a clean build and a **wrong result**. Resilience4j's
|
||||
Spring integration (`resilience4j-spring6`, which is what `resilience4j-spring-boot4` actually
|
||||
depends on — see below) implements `CircuitBreakerAspect`, `BulkheadAspect`, `RateLimiterAspect`
|
||||
and friends as real `@Aspect` classes (confirmed with `unzip -l` on the jar). Spring's
|
||||
`AnnotationAwareAspectJAutoProxyCreator` needs `org.aspectj:aspectjweaver` on the classpath to
|
||||
even recognise a bean as an aspect. Without it, **zero proxies get created, for anything** —
|
||||
Resilience4j's annotations and Spring's own `@Retryable` both go completely inert, silently.
|
||||
|
||||
That is exactly what happened building this repo: `CircuitBreakerTripAndRecoverTest` failed
|
||||
with the raw `DownstreamUnavailableException` propagating straight out of `R4jPaymentService`,
|
||||
no fallback, no state tracking — because there was no proxy in front of it at all. Adding
|
||||
`org.aspectj:aspectjweaver` fixed every failing test in the same run. `spring-boot-starter-aop`
|
||||
used to bundle this for you; its replacement doesn't, and nothing tells you that.
|
||||
|
||||
## A naming trap in the dependency itself
|
||||
|
||||
`resilience4j-spring-boot4` is a real, separate artifact from `resilience4j-spring-boot3`
|
||||
(both currently at `2.4.0`) — but its own POM depends on `resilience4j-spring6`, `spring-core
|
||||
7.0.2`, `spring-context 7.0.2` and `spring-boot-autoconfigure 4.0.0`. The "spring-boot4" in the
|
||||
artifact name is about which *Boot* generation it targets, not which internal Resilience4j
|
||||
module version it's built on — that module never got renamed to "spring7". If you're grepping
|
||||
your dependency tree for "spring7" expecting to find the pieces Boot 4.1 pulls in, you won't.
|
||||
|
||||
Next: [02-circuit-breaker.md](02-circuit-breaker.md) — the Resilience4j side, verified against
|
||||
a real trip/recover run.
|
||||
Reference in New Issue
Block a user