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.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface PackAdvisor {}

2.2 Abstract Advisor

An abstract base class simplifies concrete advisor implementations.

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
}

2.3 Concrete Advisors

Time‑taken Advisor

@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";
  }
}

Log Advisor

@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";
  }
}

2.4 Proxy Bean Post‑Processor

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[] {};
  }
}

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.

@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
}

2.6 Test

Sample test endpoint:

@Resource
private PersonAdvisorService ps;
@GetMapping("/ps")
public Object ps() {
  ps.save();
  return "success";
}

Calling the test endpoint prints the execution time. After invoking the delete‑advisor API, the time‑taken output disappears, confirming the dynamic removal.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

spring-boot
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

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.