Backend Development 9 min read

Hot‑Pluggable AOP: Dynamically Adding and Removing Advice in Spring

This article explains how to implement hot‑pluggable AOP in Spring by letting users enable or disable logging advice at runtime, covering prerequisite concepts, core logic, and a complete code‑first demonstration with dynamic proxy management.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Hot‑Pluggable AOP: Dynamically Adding and Removing Advice in Spring

This article demonstrates how to implement hot‑pluggable AOP in Spring, allowing users to enable or disable logging advice at runtime instead of hard‑coding it.

It first introduces the essential AOP concepts—Advice, Advisor, Advised, and the Spring ProxyFactory —which are required to understand dynamic advice management.

The core logic shows how to add or remove an AspectJExpressionPointcutAdvisor from the bean factory, register the proxy, and manipulate advice on existing beans.

Key implementation code:

@RestControllerEndpoint(id = "proxy")
@RequiredArgsConstructor
public class ProxyMetaDefinitionControllerEndPoint {
    private final ProxyMetaDefinitionRepository proxyMetaDefinitionRepository;
    @GetMapping("listMeta")
    public List
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";
    }
}
@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;
        }
    }
}
public void installPlugin(ProxyMetaInfo proxyMetaInfo){
    if(StringUtils.isEmpty(proxyMetaInfo.getId())){
        proxyMetaInfo.setId(proxyMetaInfo.getProxyUrl() + SPIILT + proxyMetaInfo.getProxyClassName());
    }
    AopUtil.registerProxy(defaultListableBeanFactory, proxyMetaInfo);
}
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 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);
    }
}
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));
    }
}
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());
            }
        }
    }
}
@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);
    }
}
@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;
    }
}

The article then walks through a demo: creating a service and controller, preparing a logging‑aspect JAR, and using the REST endpoints to add or remove the advice at runtime, showing console logs before and after the operations.

In summary, dynamic AOP is achieved by programmatically managing Advice, Advisor, Advised, and Pointcut objects, and the same technique can be extended with other Spring AOP APIs such as TargetSource .

backendJavaAOPpluginSpringDynamic
Code Ape Tech Column
Written by

Code Ape Tech Column

Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn

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.