【动态管理日志】Spring Boot 实现 热插拔 AOP,非常实用!

现在有这么一个需求:就是我们日志的开与关是交给使用人员来控制的,而不是由我们开发人员固定写死的。大家都知道可以用aop来实现日志管理,但是如何动态的来实现日志管理呢?aop源码中的实现逻辑中有这么一个步骤,就是会依次扫描Advice的实现类,然后执行。我们要做的就是自定义一个advice的实现类然后,在用户想要开启日志的时候就把advice加到项目中来,关闭日志的时候就把advice剔除就行了。

前置知识

  • 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;@EventListenerpublic 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;@SneakyThrowspublic 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;}@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {this.beanFactory = beanFactory;}@Overridepublic 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 {@Overridepublic 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的路由定位器端点源码,一直没找到机会实现一下,就把他搬来这个示例实现一把,加深一下印象!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/590496.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

将学习自动化测试时的医药管理信息系统项目用idea运行

将学习自动化测试时的医药管理信息系统项目用idea运行 背景 学习自动化测试的时候老师的运行方式是把医药管理信息系统项目打包成war包后再放到tomcat的webapp中去运行&#xff0c;于是我想着用idea运行会方便点&#xff0c;现在记录下步骤方便以后查找最开始没有查阅资料&am…

二、Mysql数据操作[数据查询、数据更改、常见词义]

一、增删改查 1.增 格式&#xff1a;INSERT INTO TABLES(value1,value2) VALUES (1,2),(12,34); INSERT INTO test1(id, p_name, p_data) VALUES (1,1,1), (2,2,2);2.删 格式&#xff1a;DELETE FROM TABLES WHERE condition DELETE FROM test1 WHERE id1;3.改 格式&…

蓝牙物联网灯控设计方案

蓝牙技术是当前应用最广泛的无线通信技术之一&#xff0c;工作在全球通用的 2.4GHZ 的ISM 频段。蓝牙的工作距离约为 100 米&#xff0c;具有一定的穿透性&#xff0c;没有方向限制。具有低成本、抗干扰能力强、传输质量高、低功耗等特点。蓝牙技术组网比较简单&#xff0c;无需…

antd5 Layout中Header与Sider背景色注入

前言 截止本文antd5.12.5&#xff0c; 通过ConfigProvider的方式统一修改主题色&#xff0c; 会出现Sider与Header无法修改背景色的问题。 这种行为与官方提供的theme-editor中展示的样式并不一致。 https://ant-design.antgroup.com/theme-editor-cn 在theme-editor中&#…

Dubbo 的服务请求失败怎么处理?

文章目录 Dubbo 的服务请求失败怎么处理&#xff1f; Dubbo 的服务请求失败怎么处理&#xff1f; Dubbo 是一个 RPC 框架&#xff0c;它为我们的应用提供了远程通信能力的封装&#xff0c;同时&#xff0c; Dubbo 在 RPC 通信的基础上&#xff0c;逐步在向一个生态在演进&…

throw 和 throws 的区别

Java 中的异常处理除了包括捕获异常和处理异常之外&#xff0c;还包括声明异常和拋出异常&#xff0c;可以通过 throws 关键字在方法上声明该方法要拋出的异常&#xff0c;或者在方法内部通过 throw 拋出异常对象。 throws 关键字和 throw 关键字在使用上的几点区别如下&#…

【 YOLOv5】目标检测 YOLOv5 开源代码项目调试与讲解实战(3)-训练yolov5模型(本地)

训练yolov5模型&#xff08;本地&#xff09; 训练文件 train.py训练如下图 一些参数的设置weights:对于weight参数&#xff0c;可以往Default参数中填入的参数有 cfg&#xff1a;&#xff08;缩写&#xff09;cfg参数可以选择的网络模型 data对于data hyp 超参数epochs 训练多…

绍兴市越城区科研企业可以申请的科研基金和补助主要包括:

绍兴市越城区科研企业可以申请的科研基金和补助主要包括&#xff1a; 国家自然科学基金&#xff1a;主要支持基础科学研究&#xff0c;是科研项目的主要资金来源之一。浙江省科技厅科技计划项目&#xff1a;浙江省科技厅每年会发布科技计划项目指南&#xff0c;越城区内的企业…

托管在亚马逊云科技的向量数据库MyScale如何借助AWS基础设施构建稳定高效的云数据库

MyScale是一款完全托管于亚马逊云科技&#xff0c;支持SQL的高效向量数据库。MyScale的优势在于&#xff0c;它在提供与专用向量数据库相匹敌甚至优于的性能的同时&#xff0c;还支持完整的SQL语法。以下内容&#xff0c;将阐述MyScale是如何借助亚马逊云科技的基础设施&#x…

介绍Docker的基本概念和优势,以及在应用程序开发中的实际应用

Docker是一种开源的容器化平台&#xff0c;可以将软件包裹在一个独立的容器中&#xff0c;并提供一种轻量级、可移植和自包含的环境来运行应用程序。Docker的基本概念包括以下几个方面&#xff1a; 容器&#xff1a;容器是独立运行的软件包&#xff0c;包含应用程序和它所依赖的…

张量操作与线性回归

一、张量的操作&#xff1a;拼接、切分、索引和变换 &#xff08;1&#xff09;张量拼接与切分 1.1 torch.cat() 功能&#xff1a;将张量按维度dim进行拼接 • tensors: 张量序列 • dim : 要拼接的维度 torch.cat(tensors, dim0, outNone)函数用于沿着指定维度dim将多个张量…

simulink代码生成(六)——多级中断的配置

假如系统中存在多个中断&#xff0c;需要合理的配置中断的优先级与中断向量表&#xff1b;在代码生成中&#xff0c;要与中断向量表对应&#xff1b;中断相关的知识参照博客&#xff1a; DSP28335学习——中断向量表的初始化_中断向量表什么时候初始化-CSDN博客 F28335中断系…

【计算机毕业设计】SSM汽车维修预约平台

项目介绍 本项目分为前后台&#xff0c;前台为普通用户登录&#xff0c;后台为管理员登录&#xff1b; 管理员角色&#xff1a; 管理员登录,新增管理员信息,查看管理员信息,查询管理员信息,查看用户信息列表,查询用户信息,新增新闻公告,查看新闻公告,查询新闻公告,新增配件类…

SELinux 安全模型——TE

SELinux 安全模型——TE 首发公号&#xff1a;Rand_cs 通过前面的示例策略&#xff0c;大家对 SELinux 应该有那么点感觉认识了&#xff0c;从这篇开始的三篇文章讲述 SELinux 的三种安全模型&#xff0c;会涉及一些代码&#xff0c;旨在叙述 SELinux 内部的原理 SELinux 提供…

matplotlib范围曲线简例

想在画&#xff08;平均&#xff09;loss 曲线时顺便表示方差&#xff0c;即每一个 epoch 的平均 loss 用 plot 画曲线&#xff0c;而在曲线周围用一个浅色区域表示方差。效果&#xff1a; 参考 [1-3]&#xff0c;用到 matplotlib.pyplot.fill_between 函数。为显示对浅色区及…

不吹不黑,辩证看待开发者是否需要入坑鸿蒙

前言 自打华为2019年发布鸿蒙操作系统以来&#xff0c;网上各种声音百家争鸣。尤其是2023年发布会公布的鸿蒙4.0宣称不再支持Android&#xff0c;更激烈的讨论随之而来。 本文没有宏大的叙事&#xff0c;只有基于现实的考量。 通过本文&#xff0c;你将了解到&#xff1a; Har…

Python列表推导式(for表达式)及用法

for 表达式&#xff08;列表推导式&#xff09;用于利用其他区间、元组、列表等可迭代对象创建新的列表。 for 表达式的语法格式如下&#xff1a; [表达式 for 循环计数器 in 可迭代对象] 从上面的语法格式可以看出&#xff0c;for 表达式与普通 for 循环的区别有以下两点&a…

【C++】基于C++11的线程池:threadpool

1、参考 作者博客&#xff1a;https://www.cnblogs.com/lzpong/p/6397997.html 源码&#xff1a;https://github.com/lzpong/threadpool 2、源码 原理&#xff1a;利用生产者-消费者模型&#xff0c;管理一个任务队列&#xff0c;一个线程队列&#xff0c;然后每次取一个任务…

什么是JavaScript

文章目录 一、❄️什么是JavaScript&#xff1f;二、❄️JavaScript的特点三、❄️JavaScript的组成&#x1f9eb;1、核心&#xff08;ECMAScript&#xff09;&#x1f9ff;2、文档对象模型&#xff08;DOM&#xff09;&#x1f94f;3、浏览器对象模型&#xff08;BOM&#xff…

深入理解MySQL索引底层数据结构

听课问题(听完课自己查资料) 什么是二叉树 二叉树是怎么存储数据的一个链表是一个集合的数据结构 List是怎么便利找到指定下标元素为什么会快&#xff1f;什么是红黑树 红黑树是怎么存储数据的什么是B TREE 是怎么存储数据的什么是BTREE 是怎么存储数据的 疑惑答案 a. 二叉树…