• Spring AOP 使用详解及源码分析


    1. AOP 中的术语

    需要思考以下几个点:

          01 需要知道在什么地方进行切面操作

          02 需要知道切面操作的具体内容

          03 如果有多个切面操作,应该得有一个先后执行顺序

    1.1 通知(Advice)    

           通知是织入到目标类连接点上的一段程序代码。 (在连接点上进行的具体操作,如何进行增强处理的)

           切面要完成的工作被称为通知,通知定义了切面是什么以及何时使用。Spring 切面有5种类型的通知,分别是:

            01 前置通知(Before):在目标方法调用之前调用通知功能;

            02 后置通知(After):在目标方法调用之后调用通知功能;

            03 异常通知(After-throwing):在目标方法抛出异常后调用通知;

            04 返回通知(After-returning):在目标方法成功之后调用通知;

            05 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。

    1.2 连接点(Join point)

           程序执行的某个特定位置,比如某个方法调用前、调用后、方法抛出异常后,对类成员的访问以及异常处理程序块的执行等。

          连接点就是应用执行过程中能够插入切面的一个点,这个点可以是调用方法时、抛出异常时、修改某个字段时。仅作用在方法上。

    1.3 切点(Pointcut)

           如果连接点相当于数据中的记录,那么切点相当于查询条件,一个切点可以匹配多个连接点。所以切点表示一组Joinpoint,这些Jointpoint或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。

          切点是为了缩小切面所通知的连接点的范围,即切面在何处执行。通常使用明确的类和方法名称,或者利用正则表达式定义所匹配的类和方法名称来指定切点。

    1.4 切面(Aspect)

          切面是通知和切点的结合。通知和切点共同定义了切面的全部内容,它是什么、在何时何处完成其功能。

    1.5 引入(Introduction)

          引入允许我们在不修改现有类的基础上,向现有类添加新方法或属性。

    1.6 织入(Weaving)

          织入就是把切面应用到目标对象并创建新的代理对象的过程。Spring 是通过实现后置处理器 BeanPostProcessor 接口来实现织入的。也就是在 bean 完成初始化之后,通过给目标对象生成代理对象,并交由 Spring IOC 容器来接管,这样再去容器中获取到的目标对象就是已经增强过的代理对象。

         切面在指定的连接点被织入到目标对象中,在目标对象的生命周期里,有以下几个点可以进行织入:

            01 编译期:切面在目标类编译时被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的。

            02 类加载期:切面在目标类加载到JVM时被织入。这种方式需要特殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码。

            03 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。Spring AOP就是以这种方式织入切面的。

    1.7 目标对象(TargetSource)

         包含连接点的对象。也被称作被通知或被代理对象。

    1.8 代理对象(Proxy)

          包含了原始对象的代码(是在合适的位置调用目标对象的方法)和增加后的代码(Advice通知的内容)的那个对象。

         对上面的知识点,通过下面的图来总结一下,

    2. Spring 对 AOP 的支持

    Spring入门(十):Spring AOP使用讲解 - 掘金 (juejin.cn)

    2.1 动态代理

           Spring AOP 构建在动态代理之上,也就是说,Spring 运行时会为,目标对象动态创建代理对象。

            使用动态代理实现 AOP 需要四个角色:被代理的类、被代理类的接口、织入器(Proxy.newProxyInstance())、InvocationHandler。织入器使用接口反射机制生成一个代理类,然后在这个代理类中织入代码(切入逻辑)。被代理的类是 AOP 里所说的目标,InvocationHandler 是切面,包含了通知(Advice)和切点(Pointcut)。

           代理类的核心其实就是代理对象的生成,即Proxy.newProxyInstance(),其中 getProxyClass() 方法用于获取代理类,主要做了三件事:在当前类加载器的缓存里搜索是否有代理类,没有则生成代理类并缓存在本地 JVM 里。

           代理的目的是调用目标方法时转而执行 InvocationHandler 类的 invoke() 方法。

    2.2 Spring AOP 源码学习

    生气!面试官你过来,我给你手写一个Spring Aop实现! - 腾讯云开发者社区-腾讯云 (tencent.com)

         原理图如下,

    这样讲可能还是有点抽象,写个例子来辅助理解

    (1)创建一个 Student 接口

    1. package com.concert;
    2. public interface Student {
    3. void eat();
    4. void sleep();
    5. }

    (2)创建 Student接口的实现类 StudentImpl

    1. package com.concert;
    2. public class StudentImpl implements Student {
    3. @Override
    4. public void eat() {
    5. System.out.println("吃饭啦");
    6. }
    7. @Override
    8. public void sleep() {
    9. System.out.println("该睡觉了");
    10. }
    11. }

    (3)大家都知道吃饭前要洗手、吃完饭要洗碗,怎么使用代码进行实现呢?可以定义一个 EatHandler,实现 InvocationHandler,

            利用 InvocationHandler 实现一个代理,让它去包含 Student 这个对象,那么在运行期实际上是执行这个代理的方法,然后代理再去执行真正的方法。所以我们得以在执行真正方法的前后做一些手脚。JDK 动态代理是利用反射实现的,

    1. package com.concert;
    2. import java.lang.reflect.InvocationHandler;
    3. import java.lang.reflect.Method;
    4. import java.lang.reflect.Proxy;
    5. public class EatHandler implements InvocationHandler {
    6. // 要代理的目标对象 / 被代理对象
    7. private Object target;
    8. public EatHandler(Object target){
    9. this.target = target;
    10. }
    11. public Object proxyInstance(){
    12. /**
    13. * 第一个参数是要加载代理对象的类加载器
    14. * 第二个参数是要代理类实现的接口
    15. * 第三个参数是EatHandler
    16. */
    17. return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    18. }
    19. /**
    20. * 被代理对象target调用其自身方法时执行invoke方法
    21. */
    22. @Override
    23. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    24. System.out.println("吃饭前要洗手");
    25. Object result = method.invoke(this.target, args); //调用目标对象的方法,即eat()方法
    26. System.out.println("吃完饭要洗碗");
    27. return result;
    28. }
    29. }

    (4)编写测试类

    1. package com.concert;
    2. public class ProxyMain {
    3. public static void main(String[] args) {
    4. EatHandler eatHandler = new EatHandler(new StudentImpl());
    5. Student stu1 = (Student) eatHandler.proxyInstance();
    6. Student student = new StudentImpl();
    7. stu1.eat();
    8. student.sleep();
    9. }
    10. }

    (5)输出结果

         可以看到,在“吃饭啦”前后已经插入了“吃饭前要洗手”、“吃饭后要洗碗”的功能。怎么插入的呢?

           原理就是 JDK 帮我们创建了目标对象(Student)的代理类(EatHandler),这个代理类实现了我们想要的功能。当我们调用 stu1.eat() 时,操作的是代理类,而不是目标对象,从而插入了想要插入的功能。

          代理对象继承了 Proxy 类、实现了 Student 接口。

         也就是,新创建一个类,然后在类里面创建一个对应的方法,在新创建的方法里面再具体调用目标对象的方法。

    3. AOP 代理增强时机

          bean 的创建过程中的初始化阶段的后置处理器(postProcessAfterInitialization),在满足条件的情况下会对 bean 进行 AOP 增强。核心实现是 AbstractProxyCreator 的 wraplfNecessary 方法,该方法的主要逻辑实现就是找到容器中能够应用到当前所创建的 bean 的切面,利用切面为 bean 创建代理对象。

    4. AOP 拦截链

        Spring 的 AOP 就是把切面织入到满足切点限定条件的连接点的过程。

         在 Spring 中,怎么用切面去增强一个类?是 拦截! 我们拦截方法的执行,去添加一些额外的逻辑。那如何进行拦截呢? 当然是使用动态代理!        

        Spring 如何对方法进行拦截?通过 MethodInterceptor 接口,

    1. public interface MethodInterceptor extends Interceptor {
    2. /**
    3. * Implement this method to perform extra treatments before and
    4. * after the invocation. Polite implementations would certainly
    5. * like to invoke {@link Joinpoint#proceed()}.
    6. * @param invocation the method invocation joinpoint
    7. * @return the result of the call to {@link Joinpoint#proceed()};
    8. * might be intercepted by the interceptor
    9. * @throws Throwable if the interceptors or the target object
    10. * throws an exception
    11. */
    12. Object invoke(MethodInvocation invocation) throws Throwable;
    13. }

        也就是说,被增强类(目标对象)的方法执行时,实际是通过 MethodInterceptor#invoke 被调用的。

    面试问烂的 Spring AOP 原理、SpringMVC 过程(求求你别问了) - 掘金 (juejin.cn)

  • 相关阅读:
    数组模拟堆
    【专栏】基础篇05| Redis 该怎么保证数据不丢失(下)
    金堂县中医医院二期扩建项目建设进入收尾阶段
    力扣每日一题-第25天-495.提莫攻击
    零基础选择前端还是后端?
    构建webpack知识体系 | 青训营笔记
    【自然语言处理】seq2seq模型—机器翻译
    ADB基本用法
    大话C#之WPF业务场景入门和进阶,深入浅出解析章节教程 14 项目准备上线的测试工作
    网络创业项目-网赚项目-引流量有哪些方法?
  • 原文地址:https://blog.csdn.net/fengbaobaola/article/details/126817873