Implement a Custom Circuit Breaker in Spring with AOP and Annotations
This guide explains how to build a lightweight circuit‑breaker in Spring 5.3 using a custom @PackFuse annotation and AOP, detailing its purpose, configuration options, state management, and aspect logic, and provides complete Java code examples for annotation, state class, enum, and aspect.
1. Overview
Spring Cloud provides circuit‑breaker components such as Hystrix, Resilience4j and Sentinel. A circuit breaker isolates failures, returns a predefined fallback and improves system resilience by preventing cascading faults.
Stop fault propagation by monitoring service health.
Fail fast and respond gracefully with fallback.
Provide isolation, tripping and degradation mechanisms.
2. Implementation Approach
We use AOP together with a custom annotation to add circuit‑breaker behavior to selected methods. The annotation carries configuration like error count and time window. At runtime the aspect intercepts the annotated method, creates or retrieves a circuit‑breaker state object and decides whether to proceed or return the fallback.
3. Code Implementation
3.1 Custom Annotation
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface PackFuse {
/** fallback method name */
String fallback() default "";
/** allowed failures */
int fails() default 5;
/** window size in seconds */
int windowSize() default 10;
}3.2 Circuit‑Breaker State
public enum EnumState { CLOSE, HALF_OPEN, OPEN; }
public class PackFuseState {
private EnumState state = EnumState.CLOSE;
private AtomicInteger failCount = new AtomicInteger(0);
private int maxFailCount = 5;
private int windowTime = 10;
private static final ThreadPoolExecutor executor =
new ThreadPoolExecutor(2, 2, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1));
private final Object lock = new Object();
public PackFuseState(int maxFailCount, int windowTime) {
this.maxFailCount = maxFailCount;
this.windowTime = windowTime;
executor.execute(() -> {
while (true) {
if (state == EnumState.CLOSE) {
try {
TimeUnit.SECONDS.sleep(windowTime);
if (state == EnumState.CLOSE) {
failCount.set(0);
}
} catch (InterruptedException e) { e.printStackTrace(); }
} else {
synchronized (lock) {
try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); }
}
}
}
});
}
// getters, setters omitted for brevity
public PackFuseState addFailCount() {
int count = failCount.incrementAndGet();
if (count >= maxFailCount) {
setState(EnumState.OPEN);
executor.execute(() -> {
try {
TimeUnit.SECONDS.sleep(windowTime);
setState(EnumState.HALF_OPEN);
failCount.set(0);
} catch (InterruptedException e) { e.printStackTrace(); }
});
}
return this;
}
public PackFuseState closeState() {
setState(EnumState.CLOSE);
failCount.set(0);
return this;
}
}3.3 Aspect Definition
@Aspect
@Component
public class PackFuseAspect {
private static final Map<String, PackFuseState> META_HOLDER_MAP = new ConcurrentHashMap<>();
private static final Map<String, Object> FALLBACK = new ConcurrentHashMap<>();
private static final String DEFAULT_RET_DATA = "服务不可用";
@Pointcut("@annotation(fuse)")
private void fuse(PackFuse fuse) {}
@Around("fuse(fuse)")
public Object packFuse(ProceedingJoinPoint pjp, PackFuse fuse) {
MethodSignature ms = (MethodSignature) pjp.getSignature();
Class<?> targetType = ms.getDeclaringType();
Method method = ms.getMethod();
String targetKey = getKey(targetType, method);
String fallback = fuse.fallback();
if (!FALLBACK.containsKey(targetKey)) {
if (StringUtils.hasLength(fallback)) {
try {
Method fallbackMethod = targetType.getDeclaredMethod(fallback);
FALLBACK.put(targetKey, fallbackMethod.invoke(pjp.getTarget()));
} catch (Exception e) { e.printStackTrace(); }
} else {
FALLBACK.put(targetKey, DEFAULT_RET_DATA);
}
}
int fails = fuse.fails();
int windowSize = fuse.windowSize();
PackFuseState fuseState = META_HOLDER_MAP.computeIfAbsent(
targetKey, k -> new PackFuseState(fails, windowSize));
try {
switch (fuseState.getState()) {
case CLOSE:
return pjp.proceed();
case HALF_OPEN:
Random rd = new Random();
int c = rd.nextInt(fails);
if (c >= (fails / 2)) {
Object ret = pjp.proceed();
fuseState.closeState();
synchronized (fuseState.getLock()) { fuseState.getLock().notifyAll(); }
return ret;
}
return FALLBACK.get(targetKey);
case OPEN:
return FALLBACK.get(targetKey);
}
} catch (Throwable e) {
fuseState.addFailCount();
}
return FALLBACK.get(targetKey);
}
private String getKey(Class<?> targetType, Method method) {
StringBuilder sb = new StringBuilder();
sb.append(targetType.getSimpleName());
sb.append('#').append(method.getName()).append('(');
if (method.getParameterTypes().length > 0) {
sb.deleteCharAt(sb.length() - 1);
}
return sb.append(')')
.toString()
.replaceAll("[^a-zA-Z0-9]", "");
}
}The article concludes that this simple AOP‑based circuit breaker offers flexibility and can be extended or combined with existing solutions such as Hystrix.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
