第十四章 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之假如让你来写AOP——融入IOC容器篇
第二十四章 Spring之源码阅读——AOP篇

第三部分——事务篇

第二十五章 Spring之曾经的老朋友——事务
第二十六章 Spring之假如让你来写事务——初稿篇
第二十七章 Spring之假如让你来写事务——铁三角篇
第二十八章 Spring之假如让你来写事务——属性篇
第二十九章 Spring之假如让你来写事务——状态篇
第三十章 Spring之假如让你来写事务——管理篇
第三十一章 Spring之假如让你来写事务——融入IOC容器篇
第三十二章 Spring之源码阅读——事务篇

第四部分——MVC篇

第三十三章 Spring之梦开始的地方——MVC
第三十四章 Spring之假如让你来写MVC——草图篇
第三十五章 Spring之假如让你来写MVC——映射器篇
第三十六章 Spring之假如让你来写MVC——拦截器篇
第三十七章 Spring之假如让你来写MVC——控制器篇
第三十八章 Spring之假如让你来写MVC——适配器篇
第三十九章 Spring之假如让你来写MVC——番外篇:类型转换
第四十章 Spring之假如让你来写MVC——ModelAndView篇
第四十一章 Spring之假如让你来写MVC——番外篇:数据绑定
第四十二章 Spring之假如让你来写MVC——视图篇
第四十三章 Spring之假如让你来写MVC——上传文件篇
第四十四章 Spring之假如让你来写MVC——异常处理器篇
第四十五章 Spring之假如让你来写MVC——国际化篇
第四十六章 Spring之假如让你来写MVC——主题解析器篇
第四十七章 Spring之假如让你来写MVC——闪存管理器篇
第四十八章 Spring之假如让你来写MVC——请求映射视图篇
第四十九章 Spring之假如让你来写MVC——番外篇:属性操作
第五十章 Spring之假如让你来写MVC——融入IOC容器篇
第五十一章 Spring之源码阅读——MVC篇

第五部分——Boot篇

第五十二章 Spring之再进一步——Boot
第五十三章 Spring之假如让你来写Boot——环境篇
第五十四章 Spring之假如让你来写Boot——注解篇(上)
第五十五章 Spring之假如让你来写Boot——注解篇(下)
第五十六章 Spring之假如让你来写Boot——SPI篇
第五十七章 Spring之假如让你来写Boot——配置文件篇(上)
第五十八章 Spring之假如让你来写Boot——配置文件篇(下)
第五十九章 Spring之假如让你来写Boot——番外篇:再谈Bean定义
第六十章 Spring之假如让你来写Boot——自动装配篇
第六十一章 Spring之假如让你来写Boot——番外篇:杂谈Starter
第六十二章 Spring之假如让你来写Boot——番外篇:重构BeanFactory
第六十三章 Spring之假如让你来写Boot——番外篇:再谈ApplicationContext
第六十四章 Spring之假如让你来写Boot——内嵌Web容器篇
第六十五章 Spring之假如让你来写Boot——Main方法启动篇
第六十六章 Spring之最终章——结语篇



前言

    对于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君 上传代码,准备下班咯

在这里插入图片描述


总结

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

穷儒公羊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值