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

B站学习地址



前段时间在学习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;

    // 基于注解的 pointcut
    public XdxAdvisor(Class<? extends Annotation>  annotationClass, MethodInterceptor interceptor) {

        this.advice = interceptor;
        this.pointcut = buildPointcut(annotationClass);
    }
    
    // 基于表达式的 pointcut
    public 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;
    }


    @Override
    public Pointcut getPointcut() {
        return pointcut;
    }
    @Override
    public 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) {

        // 循环每一个bean
        for (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);
        }
    }

    @Override
    public 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 {

    @Resource
    private 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")
    @XdxAnnotation
    public 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 {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Object result = invocation.proceed();

        return result + " 增强One";
    }
}

public class TwoInterceptor implements MethodInterceptor {
    @Override
    public 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
  • 7
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值