• SpringAOP高频知识梳理


    Spring AOP原理深层解析

    前言

    IOC和AOP是Spring的两个重要组成部分,IOC之前也经过分析(点击跳转)可以抽象认为这是一个容器,那AOP又是什么东西呢?

    AOP是Aspect-Oriented Programming(面向方面编程或者面向切面)的简称。它可以看成是OOP(面向对象编程)的一种延续。简单地说就是将代码中重复的部分抽取出来,在需要执行的时候使用动态代理技术,在不修改源码的基础上对方法进行增强。

    AOP的知识点特别多,这里简单整理一些比较重要的知识点概念。如果需要深度学习下去,可以参考源码分析:推荐阅读

    什么是AOP?

    (这里主要是摘录https://gyl-coder.top/spring/spring-ioc-aop/)

    下面我们先看一个 OOP 的例子。

    例如:现有三个类,HorsePigDog,这三个类中都有 eat 和 run 两个方法。

    通过 OOP 思想中的继承,我们可以提取出一个 Animal 的父类,然后将 eat 和 run 方法放入父类中,HorsePigDog通过继承Animal类即可自动获得 eat()run() 方法。这样将会少些很多重复的代码。

    OOP 编程思想可以解决大部分的代码重复问题。但是有一些问题是处理不了的。比如在父类 Animal 中的多个方法的相同位置出现了重复的代码,OOP 就解决不了。

    /**
     * 动物父类
     */
    public class Animal {
    
        /** 身高 */
        private String height;
    
        /** 体重 */
        private double weight;
    
        public void eat() {
            // 性能监控代码
            long start = System.currentTimeMillis();
    
            // 业务逻辑代码
            System.out.println("I can eat...");
    
            // 性能监控代码
            System.out.println("执行时长:" + (System.currentTimeMillis() - start)/1000f + "s");
        }
    
        public void run() {
            // 性能监控代码
            long start = System.currentTimeMillis();
    
            // 业务逻辑代码
            System.out.println("I can run...");
    
            // 性能监控代码
            System.out.println("执行时长:" + (System.currentTimeMillis() - start)/1000f + "s");
        }
    }
    

    这部分重复的代码,一般统称为 横切逻辑代码

    横切逻辑代码存在的问题:

    • 代码重复问题
    • 横切逻辑代码和业务代码混杂在一起,代码臃肿,不变维护

    AOP 就是用来解决这些问题的

    AOP 另辟蹊径,提出横向抽取机制,将横切逻辑代码和业务逻辑代码分离

    代码拆分比较容易,难的是如何在不改变原有业务逻辑的情况下,悄无声息的将横向逻辑代码应用到原有的业务逻辑中,达到和原来一样的效果。

    AOP 解决了什么问题

    通过上面的分析可以发现,AOP 主要用来解决:在不改变原有业务逻辑的情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复。

    AOP 为什么叫面向切面编程

    :指的是横切逻辑,原有业务逻辑代码不动,只能操作横切逻辑代码,所以面向横切逻辑

    :横切逻辑代码往往要影响的是很多个方法,每个方法如同一个点,多个点构成一个面。这里有一个面的概念

    重要术语

    连接点(Join point):

    • 能够被拦截的地方:Spring AOP是基于动态代理的,所以是方法拦截的。每个成员方法都可以称之为连接点~

    切点(Poincut):

    • 具体定位的连接点:上面也说了,每个方法都可以称之为连接点,我们具体定位到某一个方法就成为切点

    增强/通知(Advice):

    • 表示添加到切点的一段逻辑代码,并定位连接点的方位信息

      • 简单来说就定义了是干什么的,具体是在哪干
      • Spring AOP提供了5种Advice类型给我们:前置、后置、返回、异常、环绕给我们使用!
    • 五种Advice类型如下:

      • 前置通知(Before Advice): 在连接点之前执行的Advice,不过除非它抛出异常,否则没有能力中断执行流。使用 @Before 注解使用这个Advice。
      • 返回之后通知(After Retuning Advice): 在连接点正常结束之后执行的Advice。例如,如果一个方法没有抛出异常正常返回。通过 @AfterReturning 关注使用它。
      • 抛出(异常)后执行通知(After Throwing Advice): 如果一个方法通过抛出异常来退出的话,这个Advice就会被执行。通用 @AfterThrowing 注解来使用。
      • 后置通知(After Advice): 无论连接点是通过什么方式退出的(正常返回或者抛出异常)都会执行在结束后执行这些Advice。通过 @After 注解使用。
      • 围绕通知(Around Advice): 围绕连接点执行的Advice,就你一个方法调用。这是最强大的Advice。通过 @Around 注解使用。

    织入(Weaving):

    • 增强/通知添加到目标类的具体连接点上的过程。

    引入/引介(Introduction):

    • 引入/引介允许我们向现有的类添加新方法或属性。是一种特殊的增强!

    切面(Aspect):

    • 切面由切点和增强/通知组成,它既包括了横切逻辑的定义、也包括了连接点的定义。

    底层实现

    我们上面介绍了AOP的一系列相关术语,而这些术语其实也就是为了加深我们对AOP的面向切面的认识。AOP的底层其实是通过动态代理来实现的,其实如果学过设计模式其实看着AOP的思想也能大概猜出来了。将相同逻辑的重复代码横向抽取出来,使用动态代理技术将这些重复代码织入到目标对象方法中,实现和原来一样的功能

    在SpringAOP中,我们底层的通过两种动态代理来实现的,分别是JDK动态代理和CGLib动态代理。

    JDK动态代理

    关于JDK的动态代理代码演示可以看一下我的这一篇,顺便也可以了解一下动态代理模式(点击跳转

    JDK动态代理是需要实现某个接口了,而我们类未必全部会有接口,于是CGLib代理就有了~~

    • CGLib代理其生成的动态代理对象是目标类的子类
    • Spring AOP默认是使用JDK动态代理,如果代理的类没有接口则会使用CGLib代理。

    那么JDK代理和CGLib代理我们该用哪个呢??在《精通Spring4.x 企业应用开发实战》给出了建议:

    • 如果是单例的我们最好使用CGLib代理,如果是多例的我们最好使用JDK代理

    原因:

    • JDK在创建代理对象时的性能要高于CGLib代理,而生成代理对象的运行性能却比CGLib的低。
    • 如果是单例的代理,推荐使用CGLib

    CGLib动态代理

    字节码生成技术实现AOP,其实就是继承被代理对象,然后Override需要被代理的方法,在覆盖该方法时,自然是可以插入我们自己的代码的。CGLib动态代理需要依赖asm包,把被代理对象类的class文件加载进来,修改其字节码生成子类。

    因为需要Override被代理对象的方法,所以自然CGLIB技术实现AOP时,就 必须要求需要被代理的方法不能是final方法,因为final方法不能被子类覆盖 。

    现在演示一下如何使用CGLIB动态代理实现开头的情景,CGLIB动态代理不要求被代理类实现接口,先写一个被代理类。

    public class MonkeyOperation {
        public void put() {
            System.out.println("放入猴子...");
        }
     
        public void get() {
            System.out.println("拿出猴子...");
        }
    }
    

    在写一个类实现MethodInterceptor接口,并在接口方法intercept()里对被代理对象的方法做增强,并编写生成代理对象的方法

    public class FridgeCGLibProxy implements MethodInterceptor {
     
        public String name="hahaha";
        public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            openDoor();//调用被代理方法做一些操作
            Object result = methodProxy.invokeSuper(proxy,args);//执行被代理对象的方法,如果方法有返回值则赋值给result
            closeDoor();//调用被代理方法后做一些操作
            return result;
        }
        private void openDoor(){
            System.out.println("打开冰箱...");
        }
        private void closeDoor(){
            System.out.println("关闭冰箱...");
        }
        public Object getProxy(Class cls){//参数为被代理的类对象
            Enhancer enhancer = new Enhancer();//创建增强器,用来创建动态代理类
            enhancer.setSuperclass(cls);//设置父类,即被代理的类对象
            enhancer.setCallback(this);//设置回调,指定为当前对象
            return enhancer.create();//返回生成的代理类
        }
    }
    

    测试及结果:

      public static void main(String args[]) {
          MonkeyOperation monkeyOperation =(MonkeyOperation)new FridgeCGLibProxy().getProxy(MonkeyOperation.class);
          monkeyOperation.put();
          monkeyOperation.get();
      }
    

    应用场景

    通过使用SpringAOP,我们可以将我们与业务无关的代码,分割出来。具体的应用场景主要是有这些:

    1. 权限控制
    2. 缓存控制
    3. 事务控制
    4. 审计日志
    5. 性能监控
    6. 分布式追踪
    7. 异常处理

    如何在这些场景去使用就需要根据情况具体实践了。可以多看看实战书籍!

    参考资料

    SpringAOP就是这么简单啦

    https://gyl-coder.top/spring/spring-ioc-aop/

    Spring中的AOP原理

    《Spring技术内幕:深入解析Spring架构与原理》

  • 相关阅读:
    IO编程
    File类
    对于Java集合理解
    Java泛型
    多线程编程
    异常处理
    Static.final修饰符、super关键字及常量与变量
    java类的基本结构
    数组
    二叉树后序遍历 递归 非递归
  • 原文地址:https://www.cnblogs.com/CryFace/p/13646924.html
Copyright © 2020-2023  润新知