Backend Development 9 min read

Hot‑Pluggable AOP Implementation in Spring: Dynamically Managing Advice

This article demonstrates how to implement hot‑pluggable AOP in Spring by allowing users to dynamically add or remove advice at runtime, covering prerequisite concepts, core logic, complete code examples, and test scenarios that show logging activation and deactivation without restarting the application.

Architect's Guide
Architect's Guide
Architect's Guide
Hot‑Pluggable AOP Implementation in Spring: Dynamically Managing Advice

Requirement

Users need the ability to turn logging on or off dynamically, rather than having developers hard‑code the AOP advice; the solution is to add or remove advice at runtime.

Prerequisite Knowledge

Key Spring AOP concepts are introduced: Advice (org.aopalliance.aop.Advice), Advisor (org.springframework.aop.Advisor), Advised (org.springframework.aop.framework.Advised), and the proxy factory configuration class (org.springframework.aop.framework.Advised).

Hot‑Plug AOP Core Logic

The core mechanism scans Advice implementations, registers or deregisters them, and manipulates the proxy factory to apply or remove the advice based on user actions.

Implementation Code

Dynamic management endpoint:

@RestControllerEndpoint(id = "proxy")
@RequiredArgsConstructor
public class ProxyMetaDefinitionControllerEndPoint {
    private final ProxyMetaDefinitionRepository proxyMetaDefinitionRepository;
    @GetMapping("listMeta")
    public List<ProxyMetaDefinition> getProxyMetaDefinitions(){
        return proxyMetaDefinitionRepository.getProxyMetaDefinitions();
    }
    @GetMapping("{id}")
    public ProxyMetaDefinition getProxyMetaDefinition(@PathVariable("id") String proxyMetaDefinitionId){
        return proxyMetaDefinitionRepository.getProxyMetaDefinition(proxyMetaDefinitionId);
    }
    @PostMapping("save")
    public String save(@RequestBody ProxyMetaDefinition definition){
        try {
            proxyMetaDefinitionRepository.save(definition);
            return "success";
        } catch (Exception e) {}
        return "fail";
    }
    @PostMapping("delete/{id}")
    public String delete(@PathVariable("id") String proxyMetaDefinitionId){
        try {
            proxyMetaDefinitionRepository.delete(proxyMetaDefinitionId);
            return "success";
        } catch (Exception e) {}
        return "fail";
    }
}

Event listener for plugin installation/removal:

@RequiredArgsConstructor
public class ProxyMetaDefinitionChangeListener {
    private final AopPluginFactory aopPluginFactory;
    @EventListener
    public void listener(ProxyMetaDefinitionChangeEvent event){
        ProxyMetaInfo info = aopPluginFactory.getProxyMetaInfo(event.getProxyMetaDefinition());
        switch (event.getOperateEventEnum()){
            case ADD:
                aopPluginFactory.installPlugin(info);
                break;
            case DEL:
                aopPluginFactory.uninstallPlugin(info.getId());
                break;
        }
    }
}

Plugin installation core:

public void installPlugin(ProxyMetaInfo proxyMetaInfo){
    if(StringUtils.isEmpty(proxyMetaInfo.getId())){
        proxyMetaInfo.setId(proxyMetaInfo.getProxyUrl() + SPIILT + proxyMetaInfo.getProxyClassName());
    }
    AopUtil.registerProxy(defaultListableBeanFactory, proxyMetaInfo);
}

Proxy registration and advice addition/removal:

public static void registerProxy(DefaultListableBeanFactory beanFactory, ProxyMetaInfo proxyMetaInfo){
    AspectJExpressionPointcutAdvisor advisor = getAspectJExpressionPointcutAdvisor(beanFactory, proxyMetaInfo);
    addOrDelAdvice(beanFactory, OperateEventEnum.ADD, advisor);
}
private static AspectJExpressionPointcutAdvisor getAspectJExpressionPointcutAdvisor(DefaultListableBeanFactory beanFactory, ProxyMetaInfo proxyMetaInfo){
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
    GenericBeanDefinition beanDefinition = (GenericBeanDefinition) builder.getBeanDefinition();
    beanDefinition.setBeanClass(AspectJExpressionPointcutAdvisor.class);
    AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor();
    advisor.setExpression(proxyMetaInfo.getPointcut());
    advisor.setAdvice(Objects.requireNonNull(getMethodInterceptor(proxyMetaInfo.getProxyUrl(), proxyMetaInfo.getProxyClassName())));
    beanDefinition.setInstanceSupplier(() -> advisor);
    beanFactory.registerBeanDefinition(PROXY_PLUGIN_PREFIX + proxyMetaInfo.getId(), beanDefinition);
    return advisor;
}
public static void addOrDelAdvice(DefaultListableBeanFactory beanFactory, OperateEventEnum op, AspectJExpressionPointcutAdvisor advisor){
    AspectJExpressionPointcut pointcut = (AspectJExpressionPointcut) advisor.getPointcut();
    for(String beanName : beanFactory.getBeanDefinitionNames()){
        Object bean = beanFactory.getBean(beanName);
        if(!(bean instanceof Advised)){
            if(op == OperateEventEnum.ADD){
                buildCandidateAdvised(beanFactory, advisor, bean, beanName);
            }
            continue;
        }
        Advised advised = (Advised) bean;
        boolean match = findMatchAdvised(advised.getClass(), pointcut);
        if(op == OperateEventEnum.DEL && match){
            advised.removeAdvice(advisor.getAdvice());
            log.info("Remove Advice --> {} for Bean --> {} SUCCESS", advisor.getAdvice().getClass().getName(), bean.getClass().getName());
        } else if(op == OperateEventEnum.ADD && match){
            advised.addAdvice(advisor.getAdvice());
            log.info("Add Advice --> {} for Bean --> {} SUCCESS", advisor.getAdvice().getClass().getName(), bean.getClass().getName());
        }
    }
}

Sample service and controller used in the demo:

@Service
@Slf4j
public class HelloService implements BeanNameAware, BeanFactoryAware {
    private BeanFactory beanFactory;
    private String beanName;
    @SneakyThrows
    public String sayHello(String message){
        Object bean = beanFactory.getBean(beanName);
        log.info("{} is Advised : {}", bean, bean instanceof Advised);
        TimeUnit.SECONDS.sleep(new Random().nextInt(3));
        log.info("hello:{}", message);
        return "hello:" + message;
    }
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; }
    @Override
    public void setBeanName(String name) { this.beanName = name; }
}
@RestController
@RequestMapping("hello")
@RequiredArgsConstructor
public class HelloController {
    private final HelloService helloService;
    @GetMapping("{message}")
    public String sayHello(@PathVariable("message") String message){
        return helloService.sayHello(message);
    }
}

Logging interceptor (Advice) that prints method execution details:

@Slf4j
public class LogMethodInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Object result;
        try {
            result = invocation.proceed();
        } finally {
            log.info(">>> TargetClass:{}, method:{}, args:{}",
                invocation.getThis().getClass().getName(),
                invocation.getMethod().getName(),
                Arrays.toString(invocation.getArguments()));
        }
        return result;
    }
}

Test scenarios:

Scenario 1 – No advice added: calling http://localhost:8080/hello/zhangsan shows only the service output.

Scenario 2 – Add advice via POST to the /proxy/save endpoint, then call the same URL; console displays the logging interceptor output, confirming the proxy is active.

Delete the advice via POST to /proxy/delete/{id} ; subsequent calls no longer produce interceptor logs, proving successful removal.

Summary

Understanding the relationships among Advice, Advisor, Advised, and Pointcut is essential for implementing hot‑plug AOP; dynamic class loading may be required when the plugin JAR is not on the classpath. The example uses Spring’s AOP APIs to manually register and deregister proxies, though alternative techniques such as TargetSource are also possible.

backendJavaAOPSpringAdviceDynamic Plugin
Architect's Guide
Written by

Architect's Guide

Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.

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.