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

第十四章 Spring之假如让你来写AOP——雏形篇

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容器
      • 第十六版 AOP雏形
          • 哪些地方可以增强?
          • 如何找到这些地方?
          • 找到后要怎么增强?
          • 多个增强如何管理?
          • 如何对其进行增强?
  • 总结


前言

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

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


    书接上回,在上篇 第十三章 Spring之假如让你来写AOP——AOP联盟篇 中,A君 先把 AOP联盟 规范的接口定义好了。接下来看看 A君 会有什么骚操作吧

尝试动手写IOC容器

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

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

    前情提要: 昨天,A君 定义了 AOP联盟 规范的接口 。。。

第十六版 AOP雏形

    俗话说:人无远虑,必有近忧。 这句话用来描述 A君 此时的状态再合适不过了。 A君 现在 看着这一堆接口,一脸懵逼,这要怎么继续。纠结了良久,A君 按照之前的思路走:

  1. 哪些地方可以增强?
  2. 如何找到这些地方?
  3. 找到后要怎么增强?
  4. 多个增强如何管理?
  5. 如何对其进行增强?
哪些地方可以增强?

    目标既然已经确定,A君 开始着手准备实现 Joinpoint(连接点) 相关内容。按照之前 A君 了解到的知识:Joinpoint(连接点) 就是定义 AOP 对目标对象可以增强的地方。 A君 的计划是实现方法增强即可,也就是 Joinpoint(连接点) 就是方法,即需要实现 MethodInvocation 接口,这点不再有任何疑虑。确定完需要实现的接口,接着还要确定一下类需要实现的功能,这不经让 A君 一阵皱眉。Joinpoint(连接点) 无疑是方法执行的地方,如果仅仅执行目标方法,显然不够。需要加上 Advice(通知),那么有多个 Advice(通知) 怎么解决?

    思来想去,A君 决定让 Joinpoint(连接点) 接收所有 Advice(通知) ,然后通过链式调用去执行每一个 Advice(通知)。思路清晰后,A君 撸袖子开干,定义 ReflectiveMethodInvocation 类,代码如下:

import com.hqd.ch03.v16.aopalliance.intercept.MethodInterceptor;
import com.hqd.ch03.v16.aopalliance.intercept.MethodInvocation;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Method;
import java.util.List;

/**
 * 连接点类,用以执行通知及目标方法
 */
public class ReflectiveMethodInvocation implements MethodInvocation {
    private Object proxy;
    private Method method;
    private Object[] args;
    private List<MethodInterceptor> mis;
    private int currentInterceptorIndex = -1;

    public ReflectiveMethodInvocation(Object proxy, Method method,
                                      Object[] args, List<MethodInterceptor> mis) {
        this.proxy = proxy;
        this.method = method;
        this.args = args;
        this.mis = mis;
    }

    @Nonnull
    @Override
    public Method getMethod() {
        return method;
    }

    @Nonnull
    @Override
    public Object[] getArguments() {
        return args;
    }

    @Nullable
    @Override
    public Object proceed() throws Throwable {
        /**
         * 链式调用通知
         */
        if (mis.size() - 1 == currentInterceptorIndex) {
            return getMethod().invoke(proxy, args);
        }
        MethodInterceptor methodInterceptor = this.mis.get(++currentInterceptorIndex);

        return methodInterceptor.invoke(this);
    }

    @Nullable
    @Override
    public Object getThis() {
        return proxy;
    }

    @Nonnull
    @Override
    public AccessibleObject getStaticPart() {
        return null;
    }
}
如何找到这些地方?

    “现在 Joinpoint(连接点) 有了,还需要一个可以找到对应 Joinpoint(连接点)Pointcut(切点) ” ,A君 思忖。对于 Pointcut(切点)A君 之前用 Spring 也有接触过,类似于:execution( .test(…)) ,这块内容 Spring 也并非自己实现,而是使用 AspectJ。想到这里,A君 决定也站在巨人的肩膀上,使用 AspectJ 实现对应的 Joinpoint(连接点) 匹配。那么 Pointcut(切点) 的就简单了,接收对应的表达式,调用 AspectJ 进行匹配即可。捋清楚之后,A君 开始撸代码,定义 AspectJExpressionPointcut 类,其代码如下:

import org.aspectj.weaver.tools.PointcutExpression;
import org.aspectj.weaver.tools.PointcutParameter;
import org.aspectj.weaver.tools.PointcutParser;
import org.aspectj.weaver.tools.PointcutPrimitive;

import javax.annotation.Nullable;
import java.util.HashSet;
import java.util.Set;

/**
 * 切点表达式匹配
 */
public class AspectJExpressionPointcut {
    private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<>();

    static {
        /**
         * 定义 AspectJ 匹配器
         */
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.ARGS);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.REFERENCE);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.THIS);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.TARGET);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.WITHIN);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ANNOTATION);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_WITHIN);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ARGS);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_TARGET);
    }

    /**
     * 切点表达式
     */
    private String expression;
    private String[] pointcutParameterNames = new String[0];
    private Class<?>[] pointcutParameterTypes = new Class<?>[0];

    public AspectJExpressionPointcut(String expression) {
        this.expression = expression;
    }


    public PointcutExpression buildPointcutExpression() {
        PointcutParser parser = initializePointcutParser(Thread.currentThread().getContextClassLoader());
        PointcutParameter[] pointcutParameters = new PointcutParameter[this.pointcutParameterNames.length];
        /**
         * 匹配对应的参数
         */
        for (int i = 0; i < pointcutParameters.length; i++) {
            pointcutParameters[i] = parser.createPointcutParameter(
                    this.pointcutParameterNames[i], this.pointcutParameterTypes[i]);
        }
        return parser.parsePointcutExpression(getExpression(),
                null, pointcutParameters);
    }

    /**
     * 初始化切点匹配器
     *
     * @param classLoader
     * @return
     */
    private PointcutParser initializePointcutParser(@Nullable ClassLoader classLoader) {
        PointcutParser parser = PointcutParser
                .getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(
                        SUPPORTED_PRIMITIVES, classLoader);
        return parser;
    }

    public String getExpression() {
        return this.expression;
    }
}
找到后要怎么增强?

注:此处理解有误,Spring 应该由于历史原因,才有不同的实现。这里大家别信,看个乐呵。后边有专门的篇章解释

    在找到 Joinpoint(连接点) 之后,接下来要解决的就是怎么增强目标方法 。 前置、后置、环绕、异常增强 这些都是耳熟能详的词,但是实现上却有些不一样。首先是 Before Advice(前置通知)After Returning Advice,这两个可以直接调用增强方法,不需要特殊的操作,而 After Throwing AdviceAfter (Finally) AdviceAround Advice(环绕通知) 却不一样,需要对方法进行更细致的操作。故而 A君 把他们分成两种,Before Advice(前置通知)After Returning Advice 直接拓展 Advice 接口,A君 照猫画虎,定义一个 BeforeAdvice 接口,作为 Before Advice(前置通知) 的标记接口,代码如下:

import com.hqd.ch03.v16.aopalliance.aop.Advice;

/**
 * 前置通知标记接口
 */
public interface BeforeAdvice extends Advice {

}

只有标记接口,显然是干不了任何事的,所以还需要定义一个真正干活的接口—— MethodBeforeAdvice,代码如下:

import javax.annotation.Nullable;
import java.lang.reflect.Method;

public interface MethodBeforeAdvice extends BeforeAdvice {
    void before(Method method, Object[] args, @Nullable Object target) throws Throwable;
}

接口有了,按照惯例,接下来就是具体实现了,不急不急。A君 经过一番观察,发现这些 Advice(通知) 都是一个套路,虽然有分前后置,但是都是先确定 Pointcut(切点) 是否匹配,在执行相应的方法。咦?有公共代码,那么他就要来了,没错,就是他——抽象类。A君 定义 AbstractAspectJAdvice 类,代码如下:

import com.hqd.ch03.v16.aopalliance.aop.Advice;

import javax.annotation.Nonnull;
import java.lang.reflect.Method;

public abstract class AbstractAspectJAdvice implements Advice {
    private Method aspectJAdviceMethod;
    private Object aspect;
    private AspectJExpressionPointcut pointcut;

    public AbstractAspectJAdvice(AspectJExpressionPointcut pointcut, Method aspectJAdviceMethod, Object aspect) {
        this.aspectJAdviceMethod = aspectJAdviceMethod;
        this.aspect = aspect;
        this.pointcut = pointcut;
    }

    protected void invokeAdviceMethod(@Nonnull Method method) {
        if (match(method)) {
            try {
                aspectJAdviceMethod.invoke(aspect);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * 判断是否匹配
     *
     * @param method
     * @return
     */
    protected boolean match(Method method) {
        return pointcut.buildPointcutExpression()
                .matchesMethodExecution(method).alwaysMatches();
    }
}

提取完公共代码,接下来就要回到正题——实现 MethodBeforeAdvice。不过这玩意确实也没有什么东西,直接调用父类方法即可。A君 定义 AspectJMethodBeforeAdvice 类,代码如下:

import com.hqd.ch03.v16.aop.framework.MethodBeforeAdvice;

import javax.annotation.Nullable;
import java.lang.reflect.Method;

public class AspectJMethodBeforeAdvice extends AbstractAspectJAdvice implements MethodBeforeAdvice {
    public AspectJMethodBeforeAdvice(AspectJExpressionPointcut pointcut, Method aspectJAdviceMethod, Object aspect) {
        super(pointcut, aspectJAdviceMethod, aspect);
    }

    @Override
    public void before(Method method, Object[] args, @Nullable Object target) throws Throwable {
        invokeAdviceMethod(method);
    }
}

“咦?不太对劲。” A君 警觉。在定义 ReflectiveMethodInvocation 类时,A君 要求通知必须要时 MethodInterceptor 类型,因为只有类型为 MethodInterceptor 整个链式调用才能玩的动,而 AspectJMethodBeforeAdvice 显然不是。没办法,套个马甲把,于是乎,A君 有定义了一个 MethodBeforeAdviceInterceptor 类,适配器?随便了。代码如下:

import com.hqd.ch03.v16.aop.framework.BeforeAdvice;
import com.hqd.ch03.v16.aop.framework.MethodBeforeAdvice;
import com.hqd.ch03.v16.aopalliance.intercept.MethodInterceptor;
import com.hqd.ch03.v16.aopalliance.intercept.MethodInvocation;
import lombok.AllArgsConstructor;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

@AllArgsConstructor
public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice {
    private final MethodBeforeAdvice advice;

    @Nullable
    @Override
    public Object invoke(@Nonnull MethodInvocation mi) throws Throwable {
        advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
        return mi.proceed();
    }
}

OK,搞定。这么一来, Joinpoint(连接点) 的链式调用就玩的转了。接下来 AfterReturningAdvice 也是同理,限于篇幅,A君 就不再列举了

    After Throwing AdviceAfter (Finally) Advice 这类通知需要对方法进行更精细的控制,所以需要实现 MethodInterceptor,而不是直接实现 AdviceA君 先对 After (Finally) Advice 进行实现,见名知意,这个应该是在 finally 代码块里面实现的, A君 定义 AspectJAfterAdvice 类,代码如下:

import com.hqd.ch03.v16.aop.framework.AfterAdvice;
import com.hqd.ch03.v16.aopalliance.intercept.MethodInterceptor;
import com.hqd.ch03.v16.aopalliance.intercept.MethodInvocation;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.lang.reflect.Method;

public class AspectJAfterAdvice extends AbstractAspectJAdvice implements MethodInterceptor, AfterAdvice {

    public AspectJAfterAdvice(AspectJExpressionPointcut pointcut, Method aspectJAdviceMethod, Object aspect) {
        super(pointcut, aspectJAdviceMethod, aspect);
    }

    @Nullable
    @Override
    public Object invoke(@Nonnull MethodInvocation mi) throws Throwable {
        try {
            return mi.proceed();
        } finally {
            invokeAdviceMethod(mi.getMethod());
        }

    }
}

After Throwing Advice 自然就是在 catch 代码块执行了,只有异常时才执行,代码类似,A君 也不列举了

多个增强如何管理?

    管理 Advice(通知) 这部分内容就比较好实现了,无非定义一个管理类,可以对 Advice(通知) 进行增删查改排序就行了,这玩意难不倒 A君A君 直接定义 AdvisedSupport 类,让它对 Advice(通知) 进行管理,代码如下:

import com.hqd.ch03.v16.aopalliance.intercept.MethodInterceptor;
import org.apache.commons.collections4.CollectionUtils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * advice管理类
 */
public class AdvisedSupport {
    private List<MethodInterceptor> advisors = new ArrayList<>();

    public void addAdvisors(Collection<MethodInterceptor> advisors) {
        if (CollectionUtils.isNotEmpty(advisors)) {
            this.advisors.addAll(advisors);
        }
    }

    public void addAdvisor(MethodInterceptor advisor) {
        if (advisor != null) {
            this.advisors.add(advisor);
        }
    }

    public List<MethodInterceptor> getAdvisors() {
        return advisors;
    }
}
如何对其进行增强?

    现在万事俱备,只剩最后一部分内容了,如何把通知和目标方法进行关联?先创建代理对象,这一步毋庸置疑。但是要怎么对代理对象进行增强,嘿嘿,这个简单,老套路,套个马甲就行了。给代理类添加个默认的拦截器,让它去执行整个通知链路。思量完毕,A君 先定义 ProxyFactory 类,作为代理工厂,用以创建代理对象,并创建默认的方法拦截器。代码如下:


import lombok.AllArgsConstructor;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.io.Serializable;
import java.lang.reflect.Method;

@AllArgsConstructor
public class ProxyFactory extends AdvisedSupport {
    private Class<?> superClass;
    private Object target;
    private Method method;

    public Object getProxy() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(superClass);
        /**
         * 添加默认拦截器,执行增强
         */
        enhancer.setCallbacks(new Callback[]{new DynamicAdvisedInterceptor(this, target, method)});
        return enhancer.create();
    }

    @AllArgsConstructor
    private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {
        private AdvisedSupport advisedSupport;
        private Object target;
        private Method method;


        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            ReflectiveMethodInvocation rmi = new ReflectiveMethodInvocation(target, this.method, null, advisedSupport.getAdvisors());
            return rmi.proceed();
        }
    }
}

至此,A君 所有的步骤都已经实现了,现在需要测试一下,来验证自己的努力的成果。测试代码如下:

@Test
    public void v16() throws Throwable {

        System.out.println("############# 第十六版:aop雏形 #############");

        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut("execution(* *.test(..))");
        
        Object ap = new AopBean();
        Method method = AopBean.class.getDeclaredMethod("test");

        AopTest aop = new AopTest();
        Method beforeTest = aop.getClass().getDeclaredMethod("beforeTest");
        Method afterTest = aop.getClass().getDeclaredMethod("afterTest");
        Method afterReturnTest = aop.getClass().getDeclaredMethod("afterReturnTest");

        List<MethodInterceptor> list = new ArrayList<>();
        //前置通知
        AspectJMethodBeforeAdvice aspectJMethodBeforeAdvice = new AspectJMethodBeforeAdvice(pointcut, beforeTest, aop);
        MethodBeforeAdviceInterceptor methodBeforeAdviceInterceptor = new MethodBeforeAdviceInterceptor(aspectJMethodBeforeAdvice);
        list.add(methodBeforeAdviceInterceptor);
        //后置通知
        list.add(new AspectJAfterAdvice(pointcut, afterTest, aop));
        //返回通知
        AspectJAfterReturningAdvice aspectJAfterReturningAdvice = new AspectJAfterReturningAdvice(pointcut, afterReturnTest, aop);
        list.add(new AfterReturningAdviceInterceptor(aspectJAfterReturningAdvice));

        ProxyFactory proxyFactory = new ProxyFactory(AopBean.class, ap, method);
        proxyFactory.addAdvisors(list);
        AopBean proxy = (AopBean) proxyFactory.getProxy();
        proxy.test();

在这里插入图片描述

从结果来看,是符合预期的,A君 打算先给 老大 看下,毕竟 老大 有自己的想法,不是个好糊弄的主。今天先到这里,A君 上传代码,准备下班咯

在这里插入图片描述

总结

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


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

相关文章:

  • 基于Spring Boot的在线性格测试系统设计与实现(源码+定制+开发)智能性格测试与用户个性分析平台、在线心理测评系统的开发、性格测试与个性数据管理系统
  • 云原生周刊:Istio 1.24.0 正式发布
  • 入侵检测算法平台部署LiteAIServer视频智能分析平台行人入侵检测算法:科技守护安全的新篇章
  • Suricata
  • 3. Sharding-Jdbc核⼼流 程+多种分⽚策略
  • datastage在升级版本到11.7之后,部分在11.3上正常执行的SP报错SQLSTATE = 22007: 本机错误代码 = -180
  • 二分查找--快速地将搜索空间减半
  • 大语言模型在序列推荐中的应用
  • MinIo在Ubantu和Java中的整合
  • 某军工变压器企业:通过集团级工业IOT平台,实现数字化转型
  • yakit远程连接(引擎部署在vps上)
  • PyAEDT:Ansys Electronics Desktop API 简介
  • Apache Doris:快速入门与实践
  • word转markdown的方法(pandoc)
  • 2024 年 10 月公链行业研报:比特币引领市场,Layer 2 竞争加剧
  • 如何在Mac上切换到JDK 17开发环境
  • windows C#-创建记录类型(上)
  • 高性能分布式缓存Redis-分布式锁与布隆过滤器
  • Python →爬虫实践
  • 如何在CentOS 7上搭建SMB服务
  • AviEar:一种基于物联网的低功耗鸟类声学监测解决方案
  • SQL HAVING子句
  • 【编码】【特征选择】【降维】
  • 大模型基础BERT——Transformers的双向编码器表示
  • Electron 项目实现下载文件监听
  • 实现高效数据集成:金蝶云星空对接MySQL的成功案例