Spring AOP

90 阅读5分钟

AOP

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加某种特定功能的一种技术。AOP实际是GoF设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,提高代码的灵活性和可扩展性,AOP可以说也是这种目标的一种实现。

在Spring中提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。

什么是 AOP


面向 切面 编程,利用 AOP 可以对 业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率

通俗描述

不通过修改源代码的方式,在主干功能里面添加新功能

以登录示例

image-20221119205750988

AOP 底层原理


AOP 底层使用 动态代理

有两种情况动态代理

  • 有接口的情况 使用 JDK 动态代理

    • 创建接口实现类代理对象,增强类的方法
    • image-20221119210454455
  • 没有 接口 的情况 使用 CGLIB 动态代理

    • 创建子类的代理对象,增强类的方法
    • image-20221119210649055

AOP - JDK 动态代理


使用 JDK 动态代理,使用 Proxy 类里面的方法 创建代理对象

image-20221119210927637

调用 newProxyInstance 方法

image-20221119211017494

方法有三个参数

  • 第一参数:类加载器
  • 第二参数:增强方法所在的类,这个类实现的接口,支持多个接口
  • 第三参数:实现这个接口 InvocationHandler ,创建 代理对象,写增强的部分

编写 JDK 动态代理代码

 public class JDKProxy {
 ​
     public static void main(String[] args) {
 ​
         //创建接口实现类代理对象
         Class[] interfaces = {UserDao.class};
 ​
         UserDaoImpl userDao = new UserDaoImpl();
         UserDao useDao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
         int result = userDao.add(1, 2);
         System.out.println(result);
 ​
     }
 }
 ​
     //创建代理对象代码
 class UserDaoProxy implements InvocationHandler {
     private Object obj;
 ​
     public UserDaoProxy(Object obj) {
         this.obj = obj;
     }
 ​
     //增强的逻辑
     @Override
     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 ​
         //方法之前
         System.out.println("方法之前执行..." + method.getName() + "传递的参数" + Arrays.toString(args));
 ​
         //被增强的方法执行
         Object res = method.invoke(obj, args);
 ​
         //方法之后
         System.out.println("方法之后执行..." + obj);
         return res;
     }
 ​
     public UserDaoProxy(UserDaoImpl userDao) {
     }
 }

AOP 术语


  • 连接点

    • 类里面有哪些方法可以被增强,这些方法 称为 连接点
  • 切入点

    • 实际被真正增强的方法,称为切入点
  • 通知(增强)

    • 实际增强的逻辑部分称为通知(增强

    • 通知有多种类型

      • 前置通知
      • 后置通知
      • 环绕通知
      • 异常通知
      • 最终通知
  • 切面

    • 是动作,把通知应用到切入点的过程

AOP 操作(准备工作


  • Spring 框架 一般都是基于 AspectJ 实现 AOP 操作

    • AspectJ 不是 Spring 的组成部分,是独立的 AOP 框架,一般把 AspectJ 和 Spring 框架一起使用,进行 AOP 操作
  • 基于 AspectJ 实现 AOP 操作

    • 基于 xml 配置文件实现
    • 基于注解方式实现
  • 在项目工程里面引入相关依赖

    • image-20221119215454364
  • 切入点表达式

    • 切入点表达式作用:知道对哪个类里面的哪个方法进行增强

    • 语法结构: execution([权限修饰符][返回类型][类全路径][方法名称]([参数列表]))

      • 对 com.cs7eric.dao.BookDao 类里面的 add 进行增强

        execution(* com.cs7eric.dao.BookDao.add(..))

      • 对 com.cs7eric.dao.BookDao 类里面的 所有方法 进行增强

        execution(* com.cs7eric.dao.BookDao.*(..))

      • 对 com.cs7eric.dao包里 所有 类里面的 所有方法 进行增强

        execution(* com.cs7eric.dao.*.*(..))

AOP 操作(AspectJ 注解


  • 创建类,在类里面定义方法
 public class User {
     public void add(){
         System.out.println("add...");
     }
 }
  • 创建增强类(编写增强逻辑

    • 在增强类里面,创建方法,让不同方法代表不同通知类型
    •  public class UserProxy {
           
           public void before(){
               System.out.println("before ...");
           }
       }
      
  • 进行通知的配置

    • 在 Spring 配置文件中,开启注解扫描

      •  <context:component-scan base-package="com.cs7eric.aop.aopanno"></context:component-scan>
        
    • 使用 注解创建 User和 UserProxy 对象

      • image-20221119224654780
      • image-20221119224719753
    • 在 增强类上面添加注释 @Aspect

      • image-20221119224836312
    • 在 spring 配置文件中 开启自动生成代理对象

      •  <!-- 开始 Aspect 生成代理对象 -->
         <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
        
  • 配置不同类型的通知

    • 在 增强类的里面,在作为 通知方法上面 添加通知类型注解,使用切入点表达式

      •  @Aspect
         @Component
         public class UserProxy {
         ​
             //前置通知
             @Before(value = "execution(* com.cs7eric.aop.aopanno.User.add(..))")
             public void before(){
                 System.out.println("before ...");
             }
         ​
             //后置通知
             @After(value = "execution(* com.cs7eric.aop.aopanno.User.add(..))")
             public void after(){
                 System.out.println("after...");
             }
         ​
             //后置通知(返回通知)
             @AfterReturning(value = "execution(* com.cs7eric.aop.aopanno.User.add(..))")
             public void afterReturning(){
                 System.out.println("afterReturning...");
             }
         ​
             //异常通知
             @AfterThrowing(value = "execution(* com.cs7eric.aop.aopanno.User.add(..))")
             public void afterThrowing(){
         ​
                 System.out.println("afterThrowing...");
             }
         ​
             //环绕通知
             @Around(value = "execution(* com.cs7eric.aop.aopanno.User.add(..))")
             public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
         ​
                 System.out.println("环绕之前");
                 proceedingJoinPoint.proceed();
                 System.out.println("环绕之后");
             }
         }
        
  • 相同切入点抽取

    •  @Pointcut(value = "execution(* com.cs7eric.aop.aopanno.User.add(..))")
       public void pointDemo(){
       ​
       }
       ​
       //前置通知
       @Before(value = "pointDemo()")
       public void before(){
           System.out.println("before ...");
       }
      
  • 有多个增强类对同一个方法进行增强,设置增强优先级

    • 在增强类上面添加注解, @Order(数字类型值) ,数字类型值越小 优先级越高

      • image-20221119231055663
  • 完全注解开发

    • 创建配置类,不需要创建 xml 配置文件

      •  @Configuration
         @ComponentScan(basePackages = {"com.cs7eric"})
         @EnableAspectJAutoProxy(proxyTargetClass = true)
         public class ConfigAop {
         }