SpringBoot 热插拔AOP,动态的实现AOP【简单易懂,有大用】

本文主要是介绍SpringBoot 热插拔AOP,动态的实现AOP【简单易懂,有大用】,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

B站学习地址


文章目录

  • 一、理论
  • 二、核心代码
    • 2-1、自定义操作类型枚举
    • 2-2、自定义 Advisor
    • 2-3、动态添加/删除advisor 工具类
    • 2-4、提供测试的 Controller
  • 三、测试
    • 3-1、自定义注解
    • 3-2、自定义拦截器
    • 3-3、测试
  • 四、源码获取


前段时间在学习sentinel和dubbo的时候,很好奇它们对应的控制台为何可以实现代码无侵入 动态的添加/删除功能

看过 @Async、@Transactionnal相关源码的朋友应该知道,这是基于动态代理去实现了,既然如此那我们是否可以实现动态的去添加/删除动态代理呢

答案是 YES,下面就来实现一个动态的添加/删除动态代理的功能,它的源码很简单,但这会打开你的新世界


动态代理后你想干嘛都行,干什么不是这里的重点,重点是控制它干和不干,所以为了简单的我就在返回值上做了点文章


先来看效果图

SpringBoot 热插拔AOP,动态的实现AOP


一、理论


在开始写代码之前,先来了解几个基本的理论知识 (重要‼️

keyvalue
advice动态代理之后要干什么,其实就是这个 advice的定义,本质上就是 Interceptor(实际上也是,因为Interceptor 继承了 advice)
pointcut不可能对所有的方法都强加上 advice(可以但没必要),所以 pointcut 就是定义拦截的规则(可以对使用了某个注解进行拦截,也可以用表达式去匹配)
advisor简单理解为 它就是包含了advice 和 pointcut 的类
advised代理后生成的代理对象,它维护了一个 List advisors,从而实现功能叠加

原理就是先把bean转换成 advised,然后添加/删除 advisor,就实现了动态的动态代理 (最重要的一句话)


也可以添加/删除 advice,但这就对bean的所有方法都代理了,在大多数情况下这不是我们想要了,比如 @Async 是想添加在哪个方法上,哪个方法就异步,而不是全部的方法都异步


advisor,advice 这俩玩意不熟悉的时候,很容易看错,我开始就是,排查半天

advised.addAdvisor();
advised.addAdvice();

二、核心代码


2-1、自定义操作类型枚举

public enum OperateEventEnum {ADD("add"),DELETE("delete");private String value;OperateEventEnum(String value) {this.value = value;}public String getValue() {return value;}
}

2-2、自定义 Advisor

import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.aop.Pointcut;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.AbstractPointcutAdvisor;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;import java.lang.annotation.Annotation;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;public class XdxAdvisor extends AbstractPointcutAdvisor {private final Advice advice;private final Pointcut pointcut;// 基于注解的 pointcutpublic XdxAdvisor(Class<? extends Annotation>  annotationClass, MethodInterceptor interceptor) {this.advice = interceptor;this.pointcut = buildPointcut(annotationClass);}// 基于表达式的 pointcutpublic XdxAdvisor(String expression, MethodInterceptor interceptor) {this.advice = interceptor;this.pointcut = buildPointcut(expression);}/*** 直接复制的 @Async 构建 pointcut的代码* @param annotationType* @return*/private Pointcut buildPointcut(Class<? extends Annotation> annotationType) {Set<Class<? extends Annotation>> annotationTypes = new LinkedHashSet(2);annotationTypes.add(annotationType);ComposablePointcut result = null;AnnotationMatchingPointcut mpc;for(Iterator var3 = annotationTypes.iterator(); var3.hasNext(); result = result.union(mpc)) {Class<? extends Annotation> asyncAnnotationType = (Class)var3.next();Pointcut cpc = new AnnotationMatchingPointcut(asyncAnnotationType, true);mpc = new AnnotationMatchingPointcut((Class)null, asyncAnnotationType, true);if (result == null) {result = new ComposablePointcut(cpc);} else {result.union(cpc);}}return (Pointcut) (result != null ? result : Pointcut.TRUE);}private Pointcut buildPointcut(String expression) {AspectJExpressionPointcut tmpPointcut = new AspectJExpressionPointcut();tmpPointcut.setExpression(expression);return  tmpPointcut;}@Overridepublic Pointcut getPointcut() {return pointcut;}@Overridepublic Advice getAdvice() {return advice;}//    private Pointcut buildPointcut(Class<? extends Annotation> annotationTypes) {
//
//        AnnotationClassFilter classFilter = new AnnotationClassFilter(annotationTypes, true);
//        return new ComposablePointcut(classFilter);
//    }}

2-3、动态添加/删除advisor 工具类

import org.springframework.aop.Advisor;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.framework.ProxyProcessorSupport;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.Configuration;@Configuration
public class DynamicProxy extends ProxyProcessorSupport implements BeanFactoryAware {private DefaultListableBeanFactory beanFactory;public void operateAdvisor(XdxAdvisor advisor, OperateEventEnum operateEventEnum) {// 循环每一个beanfor (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {Object bean = beanFactory.getBean(beanDefinitionName);// 判断当前bean是否匹配if (!isEligible(bean, advisor)) {continue;}// 判断当前bean是不是已经是代理对象了,是就直接进行 Advisor 操作if (bean instanceof Advised) {Advised advised = (Advised) bean;if(operateEventEnum == OperateEventEnum.DELETE) {advised.removeAdvisor(advisor);}else if(operateEventEnum == OperateEventEnum.ADD){advised.addAdvisor(advisor);}continue;}// 生成 Advisor 的代理对象ProxyFactory proxyFactory = new ProxyFactory();proxyFactory.addAdvisor(advisor);proxyFactory.setTarget(bean);ClassLoader classLoader = this.getProxyClassLoader();Object proxy = proxyFactory.getProxy(classLoader);// 销毁之前的bean,把新的bean注入到容器beanFactory.destroySingleton(beanDefinitionName);beanFactory.registerSingleton(beanDefinitionName, proxy);}}@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {this.beanFactory = (DefaultListableBeanFactory) beanFactory;}/*** 复制的 @Async 的匹配逻辑*/private boolean isEligible(Object bean, Advisor advisor) {return AopUtils.canApply(advisor, bean.getClass());}
}

  1. 添加的时候其实可以指定位置的,如果有需要的话,本身存储的就是List有序的
  2. @Async每次添加的时候 pos = 0 ,因为异步要先开启呀
void addAdvisor(int pos, Advisor advisor) throws AopConfigException;void addAdvice(int pos, Advice advice) throws AopConfigException;

2-4、提供测试的 Controller

import cn.hutool.core.text.CharSequenceUtil;
import com.xdx97.cli.dynamic.DynamicProxy;
import com.xdx97.cli.dynamic.OperateEventEnum;
import com.xdx97.cli.dynamic.XdxAdvisor;
import com.xdx97.cli.dynamic.annotation.XdxAnnotation;
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.Map;@RestController
@RequestMapping("/advisor")
public class AdvisorController {@Resourceprivate DynamicProxy dynamicProxy;private static Map<String, XdxAdvisor> xdxAdvisorMap = new HashMap<>();@GetMapping(value = "/add")public String add(String interceptorClass, String expression, String annotationClass) throws Exception {if(CharSequenceUtil.isAllBlank(expression, annotationClass) || CharSequenceUtil.isBlank(interceptorClass)) {return "the parameter is abnormal";}if (xdxAdvisorMap.containsKey(interceptorClass + annotationClass) || xdxAdvisorMap.containsKey(interceptorClass + expression)) {return "advisor already exists";}MethodInterceptor methodInterceptor =  (MethodInterceptor) Class.forName(interceptorClass).getDeclaredConstructor().newInstance();XdxAdvisor xdxAdvisor;// 以注解为主,有注解就用注解if (CharSequenceUtil.isNotBlank(annotationClass)) {Class<? extends Annotation> aClass = (Class<? extends Annotation>) Class.forName(annotationClass);xdxAdvisor = new XdxAdvisor(aClass, methodInterceptor);xdxAdvisorMap.put(interceptorClass + annotationClass, xdxAdvisor);} else {xdxAdvisor = new XdxAdvisor(expression, methodInterceptor);xdxAdvisorMap.put(interceptorClass + expression, xdxAdvisor);}dynamicProxy.operateAdvisor(xdxAdvisor, OperateEventEnum.ADD);return "advisor add success" ;}@GetMapping(value = "/delete")public String delete(String interceptorClass, String expression, String annotationClass) {if(CharSequenceUtil.isAllBlank(expression, annotationClass) || CharSequenceUtil.isBlank(interceptorClass)) {throw new IllegalArgumentException("参数异常");}if (!xdxAdvisorMap.containsKey(interceptorClass + annotationClass) && !xdxAdvisorMap.containsKey(interceptorClass + expression)) {return "advisor not exists";}// 以注解为主,有注解就用注解StringBuilder advisorKey = new StringBuilder(interceptorClass);if (CharSequenceUtil.isNotBlank(annotationClass)) {advisorKey.append(annotationClass);} else {advisorKey.append(expression);}XdxAdvisor xdxAdvisor = xdxAdvisorMap.get(advisorKey.toString());dynamicProxy.operateAdvisor(xdxAdvisor, OperateEventEnum.DELETE);xdxAdvisorMap.remove(advisorKey.toString());return "advisor delete success";}/*** 用来测试效果的fun* @return*/@GetMapping(value = "/fun")@XdxAnnotationpublic String fun() {return "my fun";}
}

三、测试


上面的代码就已经完成了动态的动态代理,是不是很简单?或许你现在还有点懵,别担心下面的测试,会让你脉动回来

要实现两种动态代理

  1. 基于表达式,(不会写表达式?没关系,AI助你)
  2. 基于注解的

3-1、自定义注解

细心的朋友可能已经发现了在AdvisorController.fun() 方法上面已经携带了这个注解

import java.lang.annotation.*;@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface XdxAnnotation {
}

这个注解也可以用在类上面哦,这样会把所有方法都拦截


3-2、自定义拦截器


拦截到之后总得干点什么,这里我在返回值上做文章,因为这样测试的时候效果最显著了

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;public class OneInterceptor implements MethodInterceptor {@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {Object result = invocation.proceed();return result + " 增强One";}
}public class TwoInterceptor implements MethodInterceptor {@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {Object result = invocation.proceed();return result + " 增强Two";}
}

定义2个拦截器,是为了一个给注解拦截用,一个给表达式拦截用,方便观察


3-3、测试


查看代理结果

curl --location --request GET 'http://127.0.0.1:9897/advisor/fun'

添加注解的代理

curl --location --request GET 'http://127.0.0.1:9897/advisor/add?interceptorClass=com.xdx97.cli.dynamic.inteceptor.OneInterceptor&annotationClass=com.xdx97.cli.dynamic.annotation.XdxAnnotation'

添加表达式代理

curl --location --request GET 'http://127.0.0.1:9897/advisor/add?interceptorClass=com.xdx97.cli.dynamic.inteceptor.TwoInterceptor&expression=execution(* com.xdx97.cli.AdvisorController.fun(..))' 

删除注解代理

curl --location --request GET 'http://127.0.0.1:9897/advisor/delete?interceptorClass=com.xdx97.cli.dynamic.inteceptor.OneInterceptor&annotationClass=com.xdx97.cli.dynamic.annotation.XdxAnnotation' 

删除表达式代理

curl --location --request GET 'http://127.0.0.1:9897/advisor/delete?interceptorClass=com.xdx97.cli.dynamic.inteceptor.TwoInterceptor&expression=execution(* com.xdx97.cli.AdvisorController.fun(..))'

四、源码获取


  1. 关注公众号:小道仙97
  2. 回复:dynamicProxy

这篇关于SpringBoot 热插拔AOP,动态的实现AOP【简单易懂,有大用】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1003299

相关文章

Java中流式并行操作parallelStream的原理和使用方法

《Java中流式并行操作parallelStream的原理和使用方法》本文详细介绍了Java中的并行流(parallelStream)的原理、正确使用方法以及在实际业务中的应用案例,并指出在使用并行流... 目录Java中流式并行操作parallelStream0. 问题的产生1. 什么是parallelS

C++中unordered_set哈希集合的实现

《C++中unordered_set哈希集合的实现》std::unordered_set是C++标准库中的无序关联容器,基于哈希表实现,具有元素唯一性和无序性特点,本文就来详细的介绍一下unorder... 目录一、概述二、头文件与命名空间三、常用方法与示例1. 构造与析构2. 迭代器与遍历3. 容量相关4

Java中Redisson 的原理深度解析

《Java中Redisson的原理深度解析》Redisson是一个高性能的Redis客户端,它通过将Redis数据结构映射为Java对象和分布式对象,实现了在Java应用中方便地使用Redis,本文... 目录前言一、核心设计理念二、核心架构与通信层1. 基于 Netty 的异步非阻塞通信2. 编解码器三、

C++中悬垂引用(Dangling Reference) 的实现

《C++中悬垂引用(DanglingReference)的实现》C++中的悬垂引用指引用绑定的对象被销毁后引用仍存在的情况,会导致访问无效内存,下面就来详细的介绍一下产生的原因以及如何避免,感兴趣... 目录悬垂引用的产生原因1. 引用绑定到局部变量,变量超出作用域后销毁2. 引用绑定到动态分配的对象,对象

SpringBoot基于注解实现数据库字段回填的完整方案

《SpringBoot基于注解实现数据库字段回填的完整方案》这篇文章主要为大家详细介绍了SpringBoot如何基于注解实现数据库字段回填的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以了解... 目录数据库表pom.XMLRelationFieldRelationFieldMapping基础的一些代

一篇文章彻底搞懂macOS如何决定java环境

《一篇文章彻底搞懂macOS如何决定java环境》MacOS作为一个功能强大的操作系统,为开发者提供了丰富的开发工具和框架,下面:本文主要介绍macOS如何决定java环境的相关资料,文中通过代码... 目录方法一:使用 which命令方法二:使用 Java_home工具(Apple 官方推荐)那问题来了,

Java HashMap的底层实现原理深度解析

《JavaHashMap的底层实现原理深度解析》HashMap基于数组+链表+红黑树结构,通过哈希算法和扩容机制优化性能,负载因子与树化阈值平衡效率,是Java开发必备的高效数据结构,本文给大家介绍... 目录一、概述:HashMap的宏观结构二、核心数据结构解析1. 数组(桶数组)2. 链表节点(Node

Java AOP面向切面编程的概念和实现方式

《JavaAOP面向切面编程的概念和实现方式》AOP是面向切面编程,通过动态代理将横切关注点(如日志、事务)与核心业务逻辑分离,提升代码复用性和可维护性,本文给大家介绍JavaAOP面向切面编程的概... 目录一、AOP 是什么?二、AOP 的核心概念与实现方式核心概念实现方式三、Spring AOP 的关

详解SpringBoot+Ehcache使用示例

《详解SpringBoot+Ehcache使用示例》本文介绍了SpringBoot中配置Ehcache、自定义get/set方式,并实际使用缓存的过程,文中通过示例代码介绍的非常详细,对大家的学习或者... 目录摘要概念内存与磁盘持久化存储:配置灵活性:编码示例引入依赖:配置ehcache.XML文件:配置

Java 虚拟线程的创建与使用深度解析

《Java虚拟线程的创建与使用深度解析》虚拟线程是Java19中以预览特性形式引入,Java21起正式发布的轻量级线程,本文给大家介绍Java虚拟线程的创建与使用,感兴趣的朋友一起看看吧... 目录一、虚拟线程简介1.1 什么是虚拟线程?1.2 为什么需要虚拟线程?二、虚拟线程与平台线程对比代码对比示例:三