聊聊如何实现热插拔AOP

2023-12-19

前言

之前偶然看到一篇文章 利用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.HelloServiceWith Advice -->com.github.lybgeek.interceptor.LogMethodInterceptorSUCCESS !

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

再次观察控制台

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

2、删除代理


观察控制台

########################################## Remove Advice -->com.github.lybgeek.interceptor.LogMethodInterceptorFor 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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

聊聊如何实现热插拔AOP 的相关文章

  • 使用 Exec Maven 插件分叉 Java,而不使用“exec”目标

    来自文档 https www mojohaus org exec maven plugin exec exec在单独的进程中执行程序和Java程序 exec java在同一虚拟机中执行 Java 程序 我想 fork 一个 java 程序
  • 查看Java Agent修改的Java类的源代码

    我需要了解 Java 代理如何修改我的初始类 以便我能够理解代码的作用 build gradle configurations jar archiveName agent2 jar jar manifest attributes Prema
  • JOOQ 忽略具有默认值的数据库列

    看来JOOQ完全忽略了数据库列的默认值 既不会更新 ActiveRecord 对象 也不会在 INSERT 时跳过此列 相反 它尝试将其设置为 NULL 这在 NOT NULL 列上失败 Example CREATE TABLE bug f
  • getCurrentSession 在网络中休眠

    我正在使用 hibernate 和 jsp servlet 编写一个基于 Web 的应用程序 我读过有关sessionFactory getCurrentSession and sessionFactory openSession方法 我知
  • GWT、Maven、Spring - 在 Maven 构建上获取 com.thoughtworks.qdox.parser.ParseException:语法错误

    我正在尝试集成此演示中的代码 http code google com p gwt spring starter app http code google com p gwt spring starter app 进入我的主要 Spring
  • 为什么 jar 执行的通配符在 docker CMD 中不起作用?

    我有一个Dockerfile与以下CMD启动我的 Spring Boot 应用程序 FROM java 8 jre CMD java jar app file jar 当我尝试从创建的图像启动容器时 我得到 Error Unable to
  • JAXB - 忽略元素

    有什么方法可以忽略 Jaxb 解析中的元素吗 我有一个很大的 XML 文件 如果我可以忽略其中一个大而复杂的元素 那么它的解析速度可能会快很多 如果它根本无法验证元素内容并解析文档的其余部分 即使该元素不正确 那就更好了 例如 这应该只生成
  • 使用 JDBC 连接到 PostgreSql 的本地实例

    我在 Linux 机器上有一个正在运行的 PostgreSql 本地实例 当我使用psql来自 shell 的命令我成功登录 没有任何问题 我需要通过 JDBC 连接到 PostgreSql 但我不知道我到底应该传递什么url参数为Driv
  • 无法验证 Spring Security 中 url 模式的角色

    我正在使用 spring security 3 1 7 RELEASE 和 spring 3 2 13 RELEASE 我的 spring security xml 中有如下条目
  • Android - 存储对ApplicationContext的引用

    我有一个静态 Preferences 类 其中包含一些应用程序首选项和类似的内容 可以在那里存储对 ApplicationContext 的引用吗 我需要该引用 以便我可以在不继承 Activity 的类中获取缓存文件夹和类似内容 你使用的
  • 从三点求圆心的算法是什么?

    我在圆的圆周上有三个点 pt A A x A y pt B B x B y pt C C x C y 如何计算圆心 在Processing Java 中实现它 我找到了答案并实施了一个可行的解决方案 pt circleCenter pt A
  • Tomcat 6 未从 WEB-INF/lib 加载 jar

    我正在尝试找出我的 tomcat 环境中的配置问题 我们的生产服务器正在运行 tomcat 安装并从共享 NFS 挂载读取战争 然而 当我尝试使用独立的盒子 及其配置 进行同样的战争时 我收到下面发布的错误 有趣的是 如果我将 WEB IN
  • 什么是春季里程碑?

    我必须学习使用 Maven 和 Spring 在网络上 我在不同的地方看到了术语 spring里程碑 和 spring里程碑存储库 但这是一个里程碑吗 我用谷歌搜索了一下 但没有找到满足我好奇心的定义 谁能帮我 里程碑是项目管理术语 htt
  • 文本视图不显示全文

    我正在使用 TableLayout 和 TableRow 创建一个简单的布局 其中包含两个 TextView 这是代码的一部分
  • 使用 Apache 允许 Glassfish 和 PHP 在同一服务器中协同工作

    是否可以建立从 Java 到 php 文件的桥梁 我有一个用 Java 编写的应用程序 我需要执行http piwik org http piwik org 这是用 PHP 编写的 在服务器中 我正在运行 PHP 但无法从浏览器访问 php
  • Java 中清除嵌套 Map 的好方法

    public class MyCache AbstractMap
  • Selenium 单击在 Internet Explorer 11 上不起作用

    我尝试在 Internet Explorer 上单击 selenium 但它不起作用 我努力了element click moveToElement element click build perform javascript没事了 事实上
  • 设置 TreeSet 的大小

    有没有办法像数组一样对 Java 集合中的 TreeSet 进行大小限制 例如我们在数组中 anArray new int 10 数组具有固定长度 在创建数组时必须指定该长度 A TreeSet当您向其中添加元素时会自动增长 您无法设置其大
  • 关闭扫描仪是否会影响性能

    我正在解决一个竞争问题 在问题中 我正在使用扫描仪获取用户输入 这是 2 个代码段 一个关闭扫描器 一个不关闭扫描器 关闭扫描仪 import java util Scanner public class JImSelection publ
  • GAE 无法部署到 App Engine

    我正在尝试从 Eclipse 发布 Web 应用程序 我在 GAE 上创建了四个项目 可以通过登录我的帐户并查看控制台来查看它们 我已经改变了appengine web xml到项目的应用程序 ID 如果我将其更改为 GAE 上第一个创建的

随机推荐

  • HONEYWELL 05701-A-0511 框架模块

    HONEYWELL 05701 A 0511 框架模块 HONEYWELL 05701 A 0511 框架模块 产品详情 框架模块通常是一种用于构建更大型 更复杂系统的组件 在自动化和控制系统中 框架模块可能用于提供某些基础结构或功能 以支
  • 德思特EMC RICI测试方案助您对抗电磁设备干扰!

    来源 德思特测试测量 德思特方案丨德思特EMC RICI测试方案助您对抗电磁设备干扰 原文链接 https mp weixin qq com s D8wdQr reaFG yppT8nzkw 欢迎关注虹科 为您提供最新资讯 方案背景 电磁或
  • 企业电子招标采购系统源码Spring Cloud + Spring Boot + 前后端分离 + 二次开发

    项目说明 随着公司的快速发展 企业人员和经营规模不断壮大 公司对内部招采管理的提升提出了更高的要求 在企业里建立一个公平 公开 公正的采购环境 最大限度控制采购成本至关重要 符合国家电子招投标法律法规及相关规范 以及审计监督要求 通过电子化
  • 消息队列选型:Kafka 如何实现高性能?

    在分布式消息模块中 我将对消息队列中应用最广泛的 Kafka 和 RocketMQ 进行梳理 以便于你在应用中可以更好地进行消息队列选型 另外 这两款消息队列也是面试的高频考点 所以 本文我们就一起来看一下 Kafka 是如何实现高性能的
  • 【开题报告】基于SpringBoot的幼儿园学生成长管理系统的设计与实现

    1 研究背景 随着社会的发展和人们教育观念的转变 幼儿园在孩子的成长过程中扮演着越来越重要的角色 幼儿园是孩子们接受早期教育的重要阶段 对于他们的身心发展 学习能力和社交能力的培养起着至关重要的作用 因此 建立一套科学 高效的幼儿园学生成长
  • 边缘计算:构建下一代计算基础设施的关键技术

    随着物联网 人工智能和大数据等技术的快速发展 对计算基础设施的需求越来越高 然而 传统的云计算模式存在延迟高 数据传输量大等问题 为了解决这些问题 边缘计算应运而生 本文将介绍边缘计算的概念 探讨其在构建下一代计算基础设施中的关键技术 什么
  • BENTLY 125720-01 后卡模块

    BENTLY 125720 01 后卡模块 BENTLY 125720 01 后卡模块 产品详情 Bently Nevada 的产品 尤其是振动监测和机械振动解决方案 具有以下可能的特点 高精度测量 Bently Nevada 的产品通常设
  • 做一个wiki页面是体验HTML语义的好方法

    HTML语义 如何运用语义类标签来呈现Wiki网页 在上一篇文章中 我花了大量的篇幅和你解释了正确使用语义类标签的好处和一些场景 那么 哪些场景适合用到语义类标签呢 又如何运用语义类标签呢 不知道你还记不记得在大学时代 你被导师逼着改毕业论
  • git commit提交代码时 提交类别 - 提交开头命名规范

    feat 新功能 feature fix 修补bug refactor 重构 即不是新增功能 也不是修改bug的其他代码改动 style 格式 不影响代码运行的变动 test 增加测试 docs 文档 documentation chore
  • 20231219_101701 java io演练 功能演练 保存学生姓名到记事本

    需求 程序启动后 向用户询问学生姓名 如果输入的内容是s 就保存并退出 当程序结束后 把所有的输入的学生姓名 保存到名为students txt的记事本中 一个学生的名字占一行 分析 因为接收的是中文名字 所以建议使用字符流 我们随意选择一
  • 【数据结构和算法】 K 和数对的最大数目

    其他系列文章导航 Java基础合集 数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一 题目描述 二 题解 2 1 方法一 双指针排序 三 代码 3 1 方法一 双指针排序 3
  • 20231219_093920 java 字符流写数据 FileWriter

    说明 FileWriter比起FileOutputWriter要更加好用 后者使用的时候还需要一个FileOutputStream对象 前者直接使用 示例 定义对象 FileWriter fileWriter new FileWriter
  • 比 style gan 更好的 style gan2

    上一篇博客介绍了 style gan 原理 但是 style gan 的结果会有水珠伪影 作者实验后发现是 Adain 导致的 AdaIN对每一个feature map的通道进行归一化 这样可能破坏掉feature之间的信息 当然实验证明发
  • 【工具库推荐】小程序一款阳历阴历(农历)日历组件

    展示 使用方法 组件目录如上图 调用如下图 第一步 在pages rl index json中设置引用这个日历组件 代码如下 第二步 在需要调用页面wxml文件中引用这个日历组件 并绑定相应的属性 如下图 属性解释 showDatePick
  • 策略模式在数据接收和发送场景的应用

    其他系列文章导航 Java基础合集 数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一 策略模式改进 1 1 策略模式的定义 1 2 策略模式的结构通常包括以下组成部分 1 3
  • C/C++编程:令人印象深刻的高级技巧案例

    C C 编程语言在软件开发领域有着悠久的历史 由于其高效 灵活和底层访问能力 至今仍然被广泛应用 本文将介绍一些在C C 编程中令人印象深刻的高级技巧 帮助读者提升编程水平 更加高效地使用这两种强大的编程语言 一 指针运算与内存管理 C C
  • 高可用:如何实现消息队列的 HA?

    管理学上有一个木桶理论 一只水桶能装多少水取决于它最短的那块木板 这个理论推广到分布式系统的可用性上 就是系统整体的可用性取决于系统中最容易出现故障 或者性能最低的组件 系统中的各个组件都要进行高可用设计 防止单点故障 消息队列也不例外 本
  • 开源Cloudreve云盘系统源码/ 支持本地储存+对接各大对象储存/带云盘系统安装教程/公私兼备网盘系统

    源码介绍 Cloudreve云盘系统源码 它不仅支持本地储存 而且还对接各大对象储存 附带云盘系统安装教程 轻松搭建个人网盘 拥有美观界面 云盘系统安装教程 公私兼备网盘系统 多功能仿百度网盘源码 测试环境 PHP7 1 MYSQL5 6
  • 20231219_095713 java 字符缓冲输出流 BufferedWritter

    示例 关闭资源的时候 先关缓冲流 再关字符流 定义字符缓冲输出流对象 FileWriter fileWriter new FileWriter b txt BufferedWriter bufferedWriter new Buffered
  • 聊聊如何实现热插拔AOP

    前言 之前偶然看到一篇文章 利用aop实现热拔插 类似于插件 里面的实现挺好玩 今天我们也来玩一把 前置知识 Advice org aopalliance aop Advice 通知 表示 Aspect 在特定的 Join point 采取