Backend Development 8 min read

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.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
How to Dynamically Add and Remove Spring AOP Advisors Without Restarting

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.

JavaAOPbackend developmentSpring BootDynamic Proxy
Spring Full-Stack Practical Cases
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.