How to Dynamically Add and Remove Spring AOP Advisors Without Restarting
This tutorial shows how to implement dynamic hot‑plugging of Spring AOP advisors, enabling runtime addition, replacement, or removal of Advice/Advisor objects via a custom annotation, abstract base class, proxy creator, and management API without restarting the Spring Boot application.
Environment: SpringBoot 3.3.0
1. Introduction
This article demonstrates how to implement dynamic hot‑plugging of Spring AOP advisors, allowing Advice or Advisor objects to be added, replaced, or removed at runtime without restarting the application. It works by manipulating the Advised interface that Spring proxies implement.
Scenario: the system has built‑in logging and method‑execution‑time statistics that are enabled by default. To increase flexibility, users (admins, developers, or ordinary users) should be able to toggle these features—or even specific classes—through a UI without code changes or service restarts.
2. Practical Example
2.1 Custom Annotation
The annotation marks classes that can be added to the advisor collection.
<code>@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface PackAdvisor {}
</code>2.2 Abstract Advisor
An abstract base class simplifies concrete advisor implementations.
<code>public abstract class BaseAdvisor implements PointcutAdvisor, BeanFactoryAware {
protected BeanFactory beanFactory;
private String expression;
private Advice advice;
@Override
public final Advice getAdvice() {
return this.advice;
}
@Override
public final Pointcut getPointcut() {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(this.expression);
pointcut.setBeanFactory(this.beanFactory);
return pointcut;
}
// setters
}
</code>2.3 Concrete Advisors
Time‑taken Advisor
<code>@PackAdvisor
public class TimeTookAdvisor extends BaseAdvisor {
public TimeTookAdvisor() {
setAdvice(new MethodInterceptor() {
public Object invoke(MethodInvocation invocation) throws Throwable {
long start = System.currentTimeMillis();
Object ret = invocation.proceed();
System.err.printf("业务执行耗时: %dms%n", (System.currentTimeMillis() - start));
return ret;
}
});
setExpression("execution(* com.pack..*.*(..))");
}
@Override
protected String getName() {
return "timetoook";
}
}
</code>Log Advisor
<code>@PackAdvisor
static class LogAdvisor extends BaseAdvisor {
public LogAdvisor() {
setAdvice(new MethodInterceptor() {
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("记录日志...");
Object ret = invocation.proceed();
return ret;
}
});
setExpression("execution(* com.pack..*.*(..))");
}
@Override
protected String getName() {
return "log";
}
}
</code>2.4 Proxy Bean Post‑Processor
<code>public class PackDynamicAutoProxyCreator extends AbstractAutoProxyCreator {
@Override
protected Object[] getAdvicesAndAdvisorsForBean(
Class<?> beanClass, String beanName, TargetSource customTargetSource) throws BeansException {
BeanFactory beanFactory = getBeanFactory();
if (beanFactory instanceof ConfigurableListableBeanFactory bf) {
String[] advisorNames = bf.getBeanNamesForAnnotation(PackAdvisor.class);
List<Advisor> advisors = new ArrayList<>();
for (String name : advisorNames) {
advisors.add(getBeanFactory().getBean(name, Advisor.class));
}
return advisors.toArray();
}
return new Object[] {};
}
}
</code>During container startup, Spring uses this processor to check each bean for matching pointcuts and creates proxies when necessary.
2.5 Management API
The controller lets users add or remove advisors based on request parameters.
<code>@RestController
@RequestMapping("/advisors")
public class AdvisorController {
private final ApplicationContext context;
public AdvisorController(ApplicationContext context) {
this.context = context;
}
@DeleteMapping("")
public Result deleteAdvisor(String className, String name) throws Exception {
if (StringUtils.hasLength(className) && StringUtils.hasLength(name)) {
Class<?> clazz = Class.forName(className);
Object instance = context.getBean(clazz);
if (instance instanceof Advised adv) {
adv.removeAdvisor(context.getBean(name, Advisor.class));
}
}
return new Result();
}
// add endpoint omitted
}
</code>2.6 Test
Sample test endpoint:
<code>@Resource
private PersonAdvisorService ps;
@GetMapping("/ps")
public Object ps() {
ps.save();
return "success";
}
</code>Calling the test endpoint prints the execution time. After invoking the delete‑advisor API, the time‑taken output disappears, confirming the dynamic removal.
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.