首页 > 其他分享 >聊聊如何实现热插拔AOP

聊聊如何实现热插拔AOP

时间:2023-12-19 09:56:52浏览次数:42  
标签:String beanFactory 热插拔 AOP id 聊聊 advisor proxyMetaInfo public

前言

之前偶然看到一篇文章利用aop实现热拔插(类似于插件),里面的实现挺好玩。今天我们也来玩一把

前置知识

  • Advice:

org.aopalliance.aop.Advice

“通知”,表示 Aspect 在特定的 Join point 采取的操作。包括 “around”, “before” and “after 等 Advice,大体上分为了三类:BeforeAdvice、MethodInterceptor、AfterAdvice

  • Advisor:

org.springframework.aop.Advisor

“通知者”,它持有 Advice,是 Spring AOP 的一个基础接口。 它的子接口 PointcutAdvisor 是一个功能完善接口,它涵盖了绝大部分的 Advisor。

  • Advised:

org.springframework.aop.framework.Advised

AOP 代理工厂配置类接口。提供了操作和管理 Advice 和 Advisor 的能力。它的实现类 ProxyFactory 是 Spring AOP 主要用于创建 AOP 代理类的核心类。

热插拔AOP执行核心逻辑

核心实现代码

1、动态管理advice端点实现

@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、利用事件监听机制捕获安装或者卸载插件

@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、安装插件

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

4、安装插件核心实现

 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((Supplier<AspectJExpressionPointcutAdvisor>) () -> advisor);
        beanFactory.registerBeanDefinition(PROXY_PLUGIN_PREFIX + proxyMetaInfo.getId(),beanDefinition);

        return advisor;
    }

5、卸载插件

   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、卸载插件核心实现

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


        }
    }

热插拔AOP演示示例

1、创建一个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、创建一个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、准备一个日志切面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、测试

场景一:未添加切面时
浏览器访问:http://localhost:8080/hello/zhangsan
观察控制台

场景二:通过postman动态操作代理

1、新增代理


观察控制台

: ########################################## BuildCandidateAdvised -->【com.github.lybgeek.aop.test.hello.service.HelloService】 With Advice -->【com.github.lybgeek.interceptor.LogMethodInterceptor】 SUCCESS !

此时浏览器访问:http://localhost:8080/hello/zhangsan

再次观察控制台

出现了切面日志信息,说明代理生效

2、删除代理


观察控制台

########################################## Remove Advice -->【com.github.lybgeek.interceptor.LogMethodInterceptor】 For Bean -->【com.github.lybgeek.aop.test.hello.service.HelloService$$EnhancerBySpringCGLIB$$7bc75aa3】 SUCCESS !

此时浏览器访问:http://localhost:8080/hello/zhangsan

再次观察控制台


此时没有出现切面日志信息,说明代理删除成功

总结

本文实现热插拔AOP就在于对advice、advised、advisor、pointcut概念的理解,这是实现热插拔AOP的前提,其次就是对自定义classloader也需要有一定的了解,因为我们jar不一定从classpath底下加载,也有可能来源其他地方,比如远程链接啥的,最后就是把原先spring自动帮我们实现aop,我们利用相关的api,自己手动实现一遍,示例代码的api只是利用spring api其中一种实现方式,它还有多种实现方式,比如可以利用TargetSource,感兴趣的朋友,也可以自己实现一把。

至于那个代理增删改查端点contoller,是我之前看springcloud gateway的路由定位器端点源码,一直没找到机会实现一下,就把他搬来这个示例实现一把,加深一下印象

demo链接

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-dynamic-aop

标签:String,beanFactory,热插拔,AOP,id,聊聊,advisor,proxyMetaInfo,public
From: https://www.cnblogs.com/linyb-geek/p/17684466.html

相关文章

  • 聊聊神经网络的优化算法
    优化算法主要用于调整神经网络中的超参数,使得训练数据集上的损失函数尽可能小。其核心逻辑是通过计算损失函数对参数的梯度(导数)来确定参数更新方向。SGDStochasticGradientDescent(随机梯度下降法):随机梯度下降算法是一种改进的梯度下降方法,它在每次更新参数时,只随机选择一个......
  • SpringIOC和AOP机制的学习
    SpringIOC和AOP机制的学习5、HelloSpring_哔哩哔哩_bilibilispringIOC依赖code-block直接导入mvc依赖也可以<dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><......
  • C++ AOP 编程介绍
    AOP(Aspect-OrientedProgramming)是一种编程范式,将程序的非核心逻辑都“横切”处理,实现非核心逻辑与核心逻辑的分离【1】在日常工作中,会遇到一类需求:统计业务处理的耗时或者加锁,业务函数可以动态替换而非侵入式修改业务函数;简单粗暴的方法是:RetProcess(...)//业务函数{......
  • AOP
    2023-12-1616:42:561.AOP1.1作用保证开发者不修改源代码的前提下,去为系统中的业务组件添加某种通用功能。1.2注解声明切入点  ①   使用execution指示器选择方法,方法表达式以*号开始,标识我们不关心方法的返回值类型。然后我们指定了全限定类名和方法名。对于方法......
  • 聊聊GLM基座模型的理论知识
    概述大模型有两个流程:预训练和推理。预训练是在某种神经网络模型架构上,导入大规模语料数据,通过一系列的神经网络隐藏层的矩阵计算、微分计算等,输出权重,学习率,模型参数等超参数信息。推理是在预训练的成果上,应用超参数文件,基于预训练结果,根据用户的输入信息,推理预测其行为。G......
  • Spring系列:基于Spring-AOP和Spring-Aspects实现AOP切面编程
    目录一、概念及相关术语概念相关术语①横切关注点②通知(增强)③切面④目标⑤代理⑥连接点⑦切入点作用二、基于注解的AOP技术说明准备工作创建切面类并配置各种通知切入点表达式语法重用切入点表达式获取通知的相关信息环绕通知切面的优先级三、基于XML的AOP准备工作实现一、概念......
  • springboot+vue小白升级之路13-AOP实现登录、增删改查操作日志管理
    还是接着上一个的内容,我把这个新增的关于日志的功能代码都贴出来,供大家学习参考。数据库数据库droptableifexistsan_log;createtablean_log( idintnotnullauto_incrementprimarykeycomment'主键id', namevarchar(255)notnullcomment'操作内容', log_dateda......
  • Spring AOP中的不同类型的自动代理实现方式:BeanNameAutoProxyCreator DefaultAdvisorA
    “BeanNameAutoProxyCreator”、“DefaultAdvisorAutoProxyCreator”和“Metadataautoproxying”是SpringAOP中的不同类型的自动代理实现方式。BeanNameAutoProxyCreator:BeanNameAutoProxyCreator是一个自动代理创建器,它根据bean的名称进行自动代理。在配置中,你可以指定一组目......
  • 从浏览器原理出发聊聊 Chrome 插件
    浏览器架构演进单进程浏览器时代单进程浏览器是指浏览器的所有功能模块都是运行在同一个进程里,这些模块包含了网络、插件、JavaScript运行环境、渲染引擎和页面等。在2007年之前,市面上浏览器都是单进程的。单进程浏览器的架构很多功能模块运行在一个进程里,是导致单进程浏......
  • SpringAOP的组成包含哪些
    SpringAOP的组成包含哪些SpringAOP(面向切面编程)的主要组成部分包括以下几个关键概念:切面(Aspect):切面是横切关注点的模块化单元。它封装了横切关注点的具体实现,包括通知和切点。通常,切面是一个类,其中包含了定义在何处以及如何执行横切关注点的代码。通知(Advice):通知是切面......