Backend Development 12 min read

Dynamic Hot‑Pluggable AOP Implementation in Spring

This article explains how to let users dynamically enable or disable logging by managing Spring AOP advice at runtime, covering prerequisite concepts, core hot‑plug logic, detailed Java code for installing and removing plugins, a step‑by‑step demo, and a concise summary of the approach.

Top Architect
Top Architect
Top Architect
Dynamic Hot‑Pluggable AOP Implementation in Spring

The article describes a requirement to let users control logging on and off dynamically instead of hard‑coding it, and shows how to achieve this using Spring AOP with runtime‑added or removed advice.

1. Prerequisite Knowledge

Advice : the operation an aspect takes at a join point, including BeforeAdvice , MethodInterceptor , and AfterAdvice . Advisor : holds an Advice; the common Spring AOP interface, with PointcutAdvisor as a full‑featured sub‑interface. Advised : the AOP proxy factory configuration interface that can manage Advice and Advisor, implemented by ProxyFactory .

2. Hot‑Plug AOP Core Logic

(Image illustrating the core logic omitted for brevity.)

3. Core Implementation Code

1. Dynamic management of advice endpoint

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

2. Event listener to capture install/uninstall

@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. Register proxy core implementation

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. Destroy proxy core implementation

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

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

4. Demo

Steps: create a HelloService (implements BeanNameAware and BeanFactoryAware ), create a HelloController , prepare a logging aspect jar ( LogMethodInterceptor ), and run two scenarios.

Scenario 1 : Access http://localhost:8080/hello/zhangsan before adding the aspect – no logging output.

Scenario 2 : Use Postman to POST to the proxy endpoint to add the advice, then access the same URL – console shows logging information, confirming the proxy works.

Then delete the advice via the endpoint and verify that the logging disappears.

5. Summary

Understanding the concepts of Advice, Advisor, Advised, and Pointcut is essential for hot‑plug AOP. Knowledge of custom class loaders may be needed because the plugin jar might be loaded from non‑classpath locations. The example manually registers and deregisters Spring AOP proxies using Spring APIs; alternative methods such as TargetSource are also possible.

The article also contains promotional material for ChatGPT services, a private community, and related offers.

backendJavaAOPSpringDynamic PluginHot Plug
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

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.