Backend Development 10 min read

Implementing Hot‑Pluggable AOP in Spring: Dynamic Management of Advice and Advisors

This article demonstrates how to build a hot‑pluggable AOP solution in Spring by dynamically adding and removing Advice, Advisor, and related components through custom endpoints, event listeners, and Spring’s bean factory APIs, enabling users to control logging at runtime.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Implementing Hot‑Pluggable AOP in Spring: Dynamic Management of Advice and Advisors

Prerequisite Knowledge

Advice : Represents the operation taken at a specific join point. Common types include BeforeAdvice , MethodInterceptor , and AfterAdvice .

Advisor : Holds an Advice and is a core Spring AOP interface. Its sub‑interface PointcutAdvisor covers most use cases.

Advised : The AOP proxy factory configuration interface that provides operations for managing Advice and Advisor. Its main implementation is ProxyFactory .

Hot‑Pluggable AOP Core Logic

Diagram omitted for brevity.

Core Implementation Code

1. Dynamic management of advice 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";
    }
}

2. Event listener to capture plugin installation/removal

@RequiredArgsConstructor
public class ProxyMetaDefinitionChangeListener {
    private final AopPluginFactory aopPluginFactory;

    @EventListener
    public void listener(ProxyMetaDefinitionChangeEvent proxyMetaDefinitionChangeEvent){
        ProxyMetaInfo proxyMetaInfo = aopPluginFactory.getProxyMetaInfo(proxyMetaDefinitionChangeEvent.getProxyMetaDefinition());
        switch (proxyMetaDefinitionChangeEvent.getOperateEventEnum()){
            case ADD:
                aopPluginFactory.installPlugin(proxyMetaInfo);
                break;
            case DEL:
                aopPluginFactory.uninstallPlugin(proxyMetaInfo.getId());
                break;
        }
    }
}

3. Install plugin

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

4. Core registration logic

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

5. Uninstall plugin

public void uninstallPlugin(String id){
    String beanName = PROXY_PLUGIN_PREFIX + id;
    if(defaultListableBeanFactory.containsBean(beanName)){
        AopUtil.destoryProxy(defaultListableBeanFactory, id);
    } else {
        throw new NoSuchElementException("Plugin not found: " + id);
    }
}

6. Core removal logic

public static void destoryProxy(DefaultListableBeanFactory beanFactory, String id){
    String beanName = PROXY_PLUGIN_PREFIX + id;
    if(beanFactory.containsBean(beanName)){
        AspectJExpressionPointcutAdvisor advisor = beanFactory.getBean(beanName, AspectJExpressionPointcutAdvisor.class);
        addOrDelAdvice(beanFactory, OperateEventEnum.DEL, advisor);
        beanFactory.destroyBean(beanFactory.getBean(beanName));
    }
}

7. Add or delete advice on beans

public static void addOrDelAdvice(DefaultListableBeanFactory beanFactory, OperateEventEnum operateEventEnum, AspectJExpressionPointcutAdvisor advisor){
    AspectJExpressionPointcut pointcut = (AspectJExpressionPointcut) advisor.getPointcut();
    for(String beanDefinitionName : beanFactory.getBeanDefinitionNames()){
        Object bean = beanFactory.getBean(beanDefinitionName);
        if(!(bean instanceof Advised)){
            if(operateEventEnum == OperateEventEnum.ADD){
                buildCandidateAdvised(beanFactory, advisor, bean, beanDefinitionName);
            }
            continue;
        }
        Advised advisedBean = (Advised) bean;
        boolean isFindMatchAdvised = findMatchAdvised(advisedBean.getClass(), pointcut);
        if(operateEventEnum == OperateEventEnum.DEL){
            if(isFindMatchAdvised){
                advisedBean.removeAdvice(advisor.getAdvice());
                log.info("Remove Advice --> {} For Bean --> {} SUCCESS", advisor.getAdvice().getClass().getName(), bean.getClass().getName());
            }
        } else if(operateEventEnum == OperateEventEnum.ADD){
            if(isFindMatchAdvised){
                advisedBean.addAdvice(advisor.getAdvice());
                log.info("Add Advice --> {} For Bean --> {} SUCCESS", advisor.getAdvice().getClass().getName(), bean.getClass().getName());
            }
        }
    }
}

Hot‑Pluggable AOP Demo

1. Create a service

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

2. Create a controller

@RestController
@RequestMapping("hello")
@RequiredArgsConstructor
public class HelloController {
    private final HelloService helloService;

    @GetMapping("{message}")
    public String sayHello(@PathVariable("message") String message){
        return helloService.sayHello(message);
    }
}

3. Prepare a logging aspect JAR

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

4. Test Scenarios

Scenario 1: Without adding the aspect, access http://localhost:8080/hello/zhangsan and observe that no logging occurs.

Scenario 2: Use Postman to add a proxy via the custom endpoint. The console shows a successful registration message, and subsequent requests produce the logging output from LogMethodInterceptor , confirming that the dynamic advice is active.

Then delete the proxy using the provided endpoint. The console logs the removal, and further requests no longer produce the aspect logs, demonstrating successful hot‑unplugging.

Summary

The article implements hot‑pluggable AOP by dynamically managing advice , advised , advisor , and pointcut concepts, optionally handling custom class loaders for JARs loaded from non‑classpath locations. While the example uses Spring’s API, alternative approaches such as leveraging TargetSource are also possible.

The proxy CRUD endpoint was adapted from Spring Cloud Gateway’s routing locator source to reinforce understanding.

backendJavaAOPSpringAdviceDynamic
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

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.