当前位置: 首页 > article >正文

第二十一章 Spring之假如让你来写AOP——Weaver(织入器)篇

Spring源码阅读目录

第一部分——IOC篇

第一章 Spring之最熟悉的陌生人——IOC
第二章 Spring之假如让你来写IOC容器——加载资源篇
第三章 Spring之假如让你来写IOC容器——解析配置文件篇
第四章 Spring之假如让你来写IOC容器——XML配置文件篇
第五章 Spring之假如让你来写IOC容器——BeanFactory和FactoryBean
第六章 Spring之假如让你来写IOC容器——Scope和属性填充
第七章 Spring之假如让你来写IOC容器——属性填充特别篇:SpEL表达式
第八章 Spring之假如让你来写IOC容器——拓展篇
第九章 Spring之源码阅读——环境搭建篇
第十章 Spring之源码阅读——IOC篇

第二部分——AOP篇

第十一章 Spring之不太熟的熟人——AOP
第十二章 Spring之不得不了解的内容——概念篇
第十三章 Spring之假如让你来写AOP——AOP联盟篇
第十四章 Spring之假如让你来写AOP——雏形篇
第十五章 Spring之假如让你来写AOP——Joinpoint(连接点)篇
第十六章 Spring之假如让你来写AOP——Pointcut(切点)篇
第十七章 Spring之假如让你来写AOP——Advice(通知)上篇
第十八章 Spring之假如让你来写AOP——Advice(通知)下篇
第十九章 Spring之假如让你来写AOP——番外篇:Spring早期设计
第二十章 Spring之假如让你来写AOP——Aspect(切面)篇
第二十一章 Spring之假如让你来写AOP——Weaver(织入器)篇
第二十二章 Spring之假如让你来写AOP——Target Object(目标对象)篇


文章目录

  • Spring源码阅读目录
    • 第一部分——IOC篇
    • 第二部分——AOP篇
  • 前言
  • 尝试动手写IOC容器
      • 第二十一版 Weaver(织入器)篇
  • 总结


前言

    对于Spring一直都是既熟悉又陌生,说对它熟悉吧,平时用用没啥问题,但面试的时候被问的一脸懵逼,就很尴尬,都不好意思在简历上写着熟悉Spring了
在这里插入图片描述

    所以决定花点时间研究研究Spring的源码。主要参考的书籍是:《Spring源码深度解析(第2版)》、《Spring揭秘》、《Spring技术内幕:深入解析Spring架构与设计原理(第2版)》


    书接上回,在上篇 第二十章 Spring之假如让你来写AOP——Aspect(切面)篇 中,A君 已经完成了 Aspect(切面) 部分,对切点和通知进行了统一管理。接下来看看 A君 会有什么骚操作吧

尝试动手写IOC容器

    出场人物:A君(苦逼的开发)、老大(项目经理)

    背景:老大要求A君在一周内开发个简单的 IOC容器

    前情提要: A君 已经完成了 Aspect(切面) 部分,对切点和通知进行了统一管理 。。。

第二十一版 Weaver(织入器)篇

    “ A君,现在AOP部分已经快进入尾声了。还有个重要的部分——Weaver(织入器)。对于这部分内容,想必你或多或少都接触过。简而言之,就是创建代理对象,把对应的切面织入进去。当然啦,代理有基于JDK的,也有基于CGLIB的,这两个都要实现。A君,你有什么思路吗?” 老大 问到

    “那自然是定义个接口,包含创建代理的方法。接口代理也好,CGLIB代理也好,都遵循此接口。” A君 不假思索回答道

    “不错,可是这样子定义,使用者必须清楚代理有多少种实现,才能正确的创建出来代理对象!举个例子:以后新增一个创建代理的方式,那么使用者就得跟着改动,这在软件设计中可是大忌。创建代理中间是否少了点东西?” 老大 循循善诱道

    “工厂模式!?” A君 疑惑道

    “是的,你需要个工厂。使用者传入对应的配置信息即可,这样,即使以后有多少个实现,对于调用者来说是一样的,什么都不用改。除此之外,我看了你之前写的代码,缺少配置信息。我们可以根据是否存在接口,来推断使用哪个代理,但是不要剥夺用户自己指定配置的权利,这样用户没配置我们自己推断,配置了就以用户的为准,才能立于不败之地。” 老大 笑道

    “明白了!” A君 忽然有种醍醐灌顶的感觉:也许,任何设计都是被用户折腾出来的,逼得程序员处处考虑拓展性、灵活性

    “先这样子吧,你回去好好想想怎么做吧!” 老大 开始下逐客令

    “好的。” A君 默默的走回自己的工位。开始思索:如何实现 老大 所说的内容。后续创建代理都要依赖于配置,先从配置开始吧。可是配置要有哪些内容呢?A君 略一沉吟,列出下面几点:

  1. 是否使用类代理,在有接口的情况下,默认会使用JDK代理,但是保不准用户就是想用类代理
  2. 代理对象是否可见,正常情况下,代理对用户是黑盒,但是由于 AOP 存在自调用问题,即 this.xxx() 时切面无法正常执行,故而由此配置。例:

在这里插入图片描述
原因也简单,this是当前对象,也就是目标对象,没有经过代理对象,所以无法增强

  1. Advise 是否可见,默认为true
  2. 是否冻结,冻结后,配置无法再进行修改

A君 平时玩游戏时,总是选择简单难度,其实游戏本身是存在多种难度的,简单难度是面向普罗大众的,困难难度是为了给高玩提供的,各个阶层玩家都能有足够的游戏体验。这里配置也是一样的道理,对于普通用户来说,有些配置不需要去关心,想要掌控全部配置,那得对 AOP 有充足的认识才行。A君 定义 ProxyConfig 作为配置类,代码如下:

public class ProxyConfig {
    /**
     * advise是否可见
     */
    boolean opaque = false;
    /**
     * 允许代理对象内部通过 AopContext 访问当前代理,适用于自调用场景
     */
    boolean exposeProxy = false;
    /**
     * 是否使用CGLIB代理
     */
    private boolean proxyTargetClass = false;
    /**
     * 代理配置是否被冻结,防止运行时修改
     */
    private boolean frozen = false;

    public boolean isProxyTargetClass() {
        return this.proxyTargetClass;
    }

    public void setProxyTargetClass(boolean proxyTargetClass) {
        this.proxyTargetClass = proxyTargetClass;
    }


    public boolean isOpaque() {
        return this.opaque;
    }

    public void setOpaque(boolean opaque) {
        this.opaque = opaque;
    }

    /**
     * Return whether the AOP proxy will expose the AOP proxy for
     * each invocation.
     */
    public boolean isExposeProxy() {
        return this.exposeProxy;
    }

    public void setExposeProxy(boolean exposeProxy) {
        this.exposeProxy = exposeProxy;
    }

    public boolean isFrozen() {
        return this.frozen;
    }

    public void setFrozen(boolean frozen) {
        this.frozen = frozen;
    }

    public void copyFrom(ProxyConfig other) {
        this.proxyTargetClass = other.proxyTargetClass;
        this.exposeProxy = other.exposeProxy;
        this.frozen = other.frozen;
        this.opaque = other.opaque;
    }
}

Advised 去统一管理这些配置,Advised 接口新增方法如下:

在这里插入图片描述

    配置类简单,难得是如何根据配置类,去实现不同功能代理,不过,接口倒是可以先定义出来。A君 定义 AopProxy 作为所有代理实现的标准,代码如下:

/**
 * 创建代理接口
 */
public interface AopProxy {
    Object getProxy();

    Object getProxy(ClassLoader classLoader);
}

纠结良久,A君 打算先从 JDK代理 开始弄,相较于 CGCLIB,平时在工作中用到,比较熟悉点。 JDK代理 必须实现 InvocationHandler 接口,那入手点就在对应的方法上,在调用 invoke 方法时,如果方法是对应的增强方法,就调用对应的通知链。不过在此之前,需要给他加点料。嘿嘿,加啥料呢?主要就是针对上诉配置添加的,尽量让用户方便。主要添加三个接口,如下:

  • SpringImitationProxy 接口:添加防伪标识,标记这个代理类是由框架生成的。嘿嘿
  • Advised 接口:针对 opaque 配置,使其可以访问 AOP 的所有内容
  • DecoratingProxy 接口:这个是专门给 JDK代理 用的,用以获取目标类 Class对象

A君AopProxyUtils 工具类中,添加如下方法:

 static Class<?>[] completeProxiedInterfaces(AdvisedSupport advised, boolean decoratingProxy) {
        Class<?>[] specifiedInterfaces = advised.getProxiedInterfaces();
        if (specifiedInterfaces.length == 0) {
            Class<?> targetClass = advised.getTargetClass();
            if (targetClass != null) {
                if (targetClass.isInterface()) {//目标类是接口
                    advised.setInterfaces(targetClass);
                } else if (Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) {//类、lambda就添加对应的接口
                    advised.setInterfaces(targetClass.getInterfaces());
                }
                specifiedInterfaces = advised.getProxiedInterfaces();
            }
        }
        List<Class<?>> proxiedInterfaces = new ArrayList<>(specifiedInterfaces.length + 3);
        proxiedInterfaces.addAll(Arrays.asList(specifiedInterfaces));
        /**
         * 添加标记接口
         */
        if (!advised.isInterfaceProxied(SpringImitationProxy.class)) {
            proxiedInterfaces.add(SpringImitationProxy.class);
        }
        /**
         * 目标类可见,且不包含Advised接口
         */
        if (!advised.isOpaque() && !advised.isInterfaceProxied(Advised.class)) {
            proxiedInterfaces.add(Advised.class);
        }
        /**
         * 用于jdk代理中,获取对应的目标class
         */
        if (decoratingProxy && !advised.isInterfaceProxied(DecoratingProxy.class)) {
            proxiedInterfaces.add(DecoratingProxy.class);
        }
        return proxiedInterfaces.toArray(new Class[0]);
    }

方法没什么难度,就是根据对应的配置去添加对应的接口,料添加完了,剩下的就没有什么了,就是根据调用的方法去匹配对应的通知,有则调用整个通知链。无则直接调用目标方法。唯二需要注意的点是:第一点,需要根据 exposeProxy 配置,判断是否需要绑定到线程中。第二点,如果返回值 this,需要确定下是否需要返回代理对象。JdkDynamicAopProxy 代码如下:

import com.hqd.ch03.v21.aopalliance.intercept.MethodInvocation;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;

public class JdkDynamicAopProxy implements InvocationHandler, AopProxy {
    private final AdvisedSupport advised;

    private final Class<?>[] proxiedInterfaces;


    public JdkDynamicAopProxy(AdvisedSupport config) {
        this.advised = config;
        this.proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
    }

    @Override
    public Object getProxy() {
        return getProxy(Thread.currentThread().getContextClassLoader());
    }

    @Override
    public Object getProxy(ClassLoader classLoader) {
        return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object oldProxy = null;
        boolean setProxyContext = false;

        Object target = this.advised.getTarget();
        try {
            Object retVal;

            /**
             * 代理对象可见,绑定到线程中去
             */
            if (this.advised.exposeProxy) {
                oldProxy = AopContext.setCurrentProxy(proxy);
                setProxyContext = true;
            }


            List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, this.advised.getTargetClass());

            if (chain.isEmpty()) {
                Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
                retVal = method.invoke(target, argsToUse);
            } else {
                MethodInvocation invocation =
                        new ReflectiveMethodInvocation(target, proxy, method, args, chain.toArray());
                retVal = invocation.proceed();
            }

            Class<?> returnType = method.getReturnType();
            /**
             * 返回值是否是this,如果是this,则判断是否需要返回代理对象
             */
            if (retVal != null && retVal == target &&
                    returnType != Object.class && returnType.isInstance(proxy) &&
                    !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
                retVal = proxy;
            } else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
                throw new RuntimeException(
                        "Null return value from advice does not match primitive return type for: " + method);
            }
            return retVal;
        } finally {
            if (setProxyContext) {
                AopContext.setCurrentProxy(oldProxy);
            }
        }
    }
}

JDK代理 到这就结束了,比想象中的简单呢。” A君 暗喜

    接下来就是 CGLIB代理 了,其实和 JDK代理 逻辑差不多,在此之前,A君 通过 Callback数组 把通知织入进去,重点就在这里。JDK代理 中,是根据方法名、参数等信息去对调用对应的方法,而 CGLIB代理 则是通过 Callback数组 下标。那怎么知道数组下标呢?那自然是实现对应的接口了,这个接口就是 CallbackFilter。剩下的基本就一样了。A君 定义 ProxyCallbackFilter 类,实现 CallbackFilter 接口。代码如下:


  /**
   * 规定callbacks数组顺序
   */
 private static final int AOP_PROXY = 0;//通知方法
 private static final int INVOKE_TARGET = 1;//目标对象方法
 private static final int NO_OVERRIDE = 2;//空操作
 private static final int DISPATCH_TARGET = 3;//获取目标类class对象
 private static final int DISPATCH_ADVISED = 4;//advised 相关的方法

 private static class ProxyCallbackFilter implements CallbackFilter {
        private final AdvisedSupport advised;

        public ProxyCallbackFilter(AdvisedSupport advised) {
            this.advised = advised;
        }

        /**
         * 这里返回值就是callbacks的下标
         * 也就是说,这里是去匹配对应的回调函数
         *
         * @return
         */

        @Override
        public int accept(Method method) {
            /**
             * 空操作
             */
            if (AopUtils.isFinalizeMethod(method)) {
                return NO_OVERRIDE;
            }
            /**
             * Advised相关的方法委托给ADVISED分发器
             */
            if (!this.advised.isOpaque() && method.getDeclaringClass().isInterface() &&
                    method.getDeclaringClass().isAssignableFrom(Advised.class)) {
                return DISPATCH_ADVISED;
            }
            Class<?> targetClass = this.advised.getTargetClass();
            List<?> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
            boolean haveAdvice = !chain.isEmpty();
            boolean exposeProxy = this.advised.isExposeProxy();
            boolean isFrozen = this.advised.isFrozen();
            /**
             * 方法存在通知,使用增强拦截器
             */
            if (haveAdvice || !isFrozen) {
                return AOP_PROXY;
            } else {//不存在,调用目标方法
                if (exposeProxy) {
                    return INVOKE_TARGET;
                }
                Class<?> returnType = method.getReturnType();
                if (targetClass != null && returnType.isAssignableFrom(targetClass)) {
                    return INVOKE_TARGET;
                } else {
                    return DISPATCH_TARGET;
                }
            }
        }
    }

ProxyCallbackFilter 出来后,主体逻辑就出来了,接着只需要根据定义的顺序去定义 Callback数组 即可。A君 定义个方法,用来组装 Callback数组。代码如下:

   private Callback[] getCallbacks(Class<?> rootClass) {
        boolean exposeProxy = this.advised.isExposeProxy();
        boolean isStatic = true;

        //1.切面增强调用拦截器
        Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised);

        Callback targetInterceptor;
        /**
         * 2.代理对象拦截器,是否暴露代理对象
         * 两个拦截器的唯一区别就是,是否暴露代理对象
         */
        if (exposeProxy) {
            targetInterceptor = new StaticUnadvisedExposedInterceptor(this.advised.getTarget());
        } else {
            targetInterceptor = new StaticUnadvisedInterceptor(this.advised.getTarget());
        }

        //4.序列化拦截器
        Callback targetDispatcher = new StaticDispatcher(this.advised.getTarget());

        Callback[] mainCallbacks = new Callback[]{
                aopInterceptor,
                targetInterceptor,
                new SerializableNoOp(),
                targetDispatcher, this.advisedDispatcher
        };
        return mainCallbacks;
    }

为了文章整体的整洁性,A君 就贴主体逻辑。getProxy 方法代码如下:

public Object getProxy(ClassLoader classLoader) {
        Enhancer enhancer = new Enhancer();
        Class<?> rootClass = this.advised.getTargetClass();

        Class<?> proxySuperClass = rootClass;
        /**
         * 如果是代理类,则再往上获取父类
         */
        if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {
            proxySuperClass = rootClass.getSuperclass();
            Class<?>[] additionalInterfaces = rootClass.getInterfaces();
            for (Class<?> additionalInterface : additionalInterfaces) {
                this.advised.addInterface(additionalInterface);
            }
        }

        enhancer.setSuperclass(proxySuperClass);
        /**
         * 指定命名策略,防止冲突
         */
        enhancer.setNamingPolicy(SpringImitationNamingPolicy.INSTANCE);
        enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
        if (classLoader != null) {
            enhancer.setClassLoader(classLoader);
        }
        /**
         * 添加默认拦截器,执行增强
         */

        Callback[] callbacks = getCallbacks(rootClass);
        Class<?>[] types = new Class<?>[callbacks.length];
        for (int x = 0; x < types.length; x++) {
            types[x] = callbacks[x].getClass();
        }
        enhancer.setCallbackFilter(new ProxyCallbackFilter(
                this.advised.getConfigurationOnlyCopy()));
        enhancer.setCallbacks(callbacks);
        enhancer.setCallbackTypes(types);

        /**
         * 创建代理对象
         */
        return createProxyClassAndInstance(enhancer, callbacks);
    }

好了, CGLIB代理JDK代理 都已经实现了。就剩下一个工厂了

    工厂的实现并没有什么特殊的地方,无非就是根据配置和接口推断使用哪个代理。A君 定义 AopProxyFactory 接口。代码如下:

public interface AopProxyFactory {
    AopProxy createAopProxy(AdvisedSupport config);
}

默认实现为 DefaultAopProxyFactory。代码如下:

import com.hqd.ch03.v21.aop.SpringImitationProxy;
import com.hqd.ch03.v21.utils.ClassUtils;

import java.lang.reflect.Proxy;

public class DefaultAopProxyFactory implements AopProxyFactory {
    @Override
    public AopProxy createAopProxy(AdvisedSupport config) {
        if (config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
            Class<?> targetClass = config.getTargetClass();
            if (targetClass == null) {
                throw new RuntimeException("TargetSource cannot determine target class: " +
                        "Either an interface or a target is required for proxy creation.");
            }
            if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) {
                return new JdkDynamicAopProxy(config);
            }
            return new CglibAopProxy(config);
        } else {
            return new JdkDynamicAopProxy(config);
        }
    }

    private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
        Class<?>[] ifcs = config.getProxiedInterfaces();
        return (ifcs.length == 0 || (ifcs.length == 1 && SpringImitationProxy.class.isAssignableFrom(ifcs[0])));
    }
}

    至此整个 Weaver(织入器) 就全部完事了。A君 按捺不住心中的喜悦,开始编写测试代码。代码如下:

    @Test
    public void v21() throws Throwable {
        System.out.println("############# 第二十一版 Weaver(织入器)篇 #############");
        System.out.println("############# 接口代理 #############");
        com.hqd.ch03.v21.aop.framework.aspectj.AspectJExpressionPointcut pointcut =
                new com.hqd.ch03.v21.aop.framework.aspectj.AspectJExpressionPointcut("execution(* *.test*(..)) and args(msg)");
        Object ap = new AopBean();
        //前置通知
        AopTest aop = new AopTest();
        Method beforeTest = aop.getClass().getDeclaredMethod("beforeTestV19", JoinPoint.class, String.class);

        List list = new ArrayList<>();
        //前置通知
        com.hqd.ch03.v21.aop.framework.aspectj.AspectJMethodBeforeAdvice beforeAdvice =
                new com.hqd.ch03.v21.aop.framework.aspectj.AspectJMethodBeforeAdvice(pointcut, beforeTest, aop);
        com.hqd.ch03.v21.aop.Advisor advisor = new com.hqd.ch03.v21.aop.framework.aspectj.AspectJPointcutAdvisor(beforeAdvice);
        list.add(com.hqd.ch03.v21.aop.interceptor.ExposeInvocationInterceptor.ADVISOR);
        list.add(advisor);
        com.hqd.ch03.v21.aop.framework.ProxyFactory proxyFactory = new com.hqd.ch03.v21.aop.framework.ProxyFactory(ap);
        proxyFactory.addInterface(IAopBean.class);
        proxyFactory.addAdvisors(list);
        IAopBean jdkProxy = (IAopBean) proxyFactory.getProxy();
        jdkProxy.test1("111");

        System.out.println("############# cglib代理 #############");
        proxyFactory.setProxyTargetClass(true);
        AopBean cgProxy = (AopBean) proxyFactory.getProxy();
        cgProxy.test1("111");
    }

测试结果如下:

在这里插入图片描述

“好了,Weaver(织入器) 部分已经完成了,可以交差了。” A君 欢呼

总结

    正所谓树欲静而风不止,欲知后事如何,请看下回分解(✪ω✪)


http://www.kler.cn/a/399956.html

相关文章:

  • UniApp在Vue3的setup语法糖下自定义组件插槽详解
  • “乐鑫组件注册表”简介
  • 三周精通FastAPI:42 手动运行服务器 - Uvicorn Gunicorn with Uvicorn
  • 【C++】红黑树封装map—set
  • vue3 如何调用第三方npm包内部的 pinia 状态管理库方法
  • CPU执行指令的过程
  • 使用 PyTorch-BigGraph 构建和部署大规模图嵌入的完整教程
  • SError: (External) CUDA error(719), unspecified launch failure.
  • Clip结合Faiss+Flask简易版文搜图服务
  • 使用PSpice进行第一个电路的仿真
  • ACE之单例
  • 把一个对象序列化为字符串,再反序列化回来
  • cisco防火墙在内网通过外网域名进行访问的配置
  • 汽车与摩托车分类数据集
  • 【Flask+Gunicorn+Nginx】部署目标检测模型API完整解决方案
  • 【gitlab】gitlabrunner部署
  • 基于差分、粒子群算法下的TSP优化对比
  • YOLOv11融合针对小目标FFCA-YOPLO中的FEM模块及相关改进思路
  • Tailscale 自建 Derp 中转服务器
  • 【Mac】卸载JAVA、jdk
  • Day02_AJAX综合案例 (黑马笔记)
  • 在 CentOS 7 上安装 MinIO 的步骤
  • 【爬虫实战】抓取某站评论
  • 【论文笔记】SCOPE: Sign Language Contextual Processing with Embedding from LLMs
  • 代码随想录第三十四天
  • 输出比较简介