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.
94 lines
5.0 KiB
Markdown
94 lines
5.0 KiB
Markdown
# 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.
|