Spring----AOP入门介绍、原理与使用

AOP

介绍

  • AOP:面向切面编程,无入侵式编程一种编程范式,指导开发者如何组织程序结构
    • OOP:面向对象
  • 作用:在不惊动原始设计的基础上为其做功能增强

概念定义

  • Aspect(切面):描述通知与切入点的对应关系(执行位置和共性之间的关系)
    • Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
  • Joint point(连接点):程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等
    • 表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
    • 在SpringAOP中,理解为方法的执行
  • Pointcut(切入点):匹配连接点的式子
    • 表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
    • 在SpringAOP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法
  • Advice(增强 / 通知):在切入点处执行的操作,也就是共性功能
    • Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
    • 通知写入通知类中
    • 在SpringAOP中,功能最终以方法的形式呈现
  • Target(目标对象):织入 Advice 的目标对象.。
  • Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程

入门案例

例1:在接口执行前输出当前系统时间

  1. 导入坐标(pom.xml)

    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.4</version>
    </dependency>
    
  2. 制作连接点方法(原始操作,Dao接口与实现类)

    @Component("UserService")
    @Repository
    public class UserServiceImpl {
        public void save(){
            System.out.println(System.currentTimeMillis());
            System.out.println("service save");
        }
        public void update(){
            System.out.println("service update");
        }
    }
    
  3. 制作共性功能

    public class MyAdvice {
        public void method() {
            System.out.println(System.currentTimeMillis());//共性功能
        }
    }
    
  4. 定义切入点

    //class MyAdvice
    //@Pointcut("execution()"")括号内表示这个方法为切入点
    @Pointcut("execution(void spring.configTest.UserServiceImpl.update())")
    private void pt(){
    }
    
  5. 绑定切入点与增强的关系

    //class MyAdvice
    //@Before()表示当发现切入点时,在切入点执行之前使用增强
    @Before("pt()")
    public void method() {
        System.out.println(System.currentTimeMillis());
    }
    
  6. 让Spring识别AOP

    1. 在通知类前加入@Component@Aspect

      @Component
      @Aspect
      public class MyAdvice {
          ...
      }
      
    2. 在配置类前加入@EnableAspectJAutoProxy

      @Configuration
      @ComponentScan(basePackages = "spring.configTest")
      @EnableAspectJAutoProxy
      public class SpringConfig {
          ...
      }
      
  • 运行结果:userService.save()userService.update() 一致

AOP工作流程&核心概念

  • 工作流程

    1. Spring容器启动

    2. 读取所有切面配置中的切入点

    3. 初始化bean,判定bean对应的类中的方法是否匹配到任意切入点

      • 匹配失败,创建对象

      • 匹配成功,创建原始对象(目标对象)的代理对象

    4. 获取bean执行方法

      • 获取bean,调用方法并执行,完成操作

      • 获取的ben是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作

  • 核心概念

    • 目标对象(Target):原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作的

    • 代理(Proxy):目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现

  • SpringAOP的本质:代理模式

AOP切入点表达式

  • 切入点:要进行增强的方法

  • 切入点表达式:要进行增强的方法的描述方法

  • 通知的类型

    • @Before:切入点方法运行前运行
    • @After:切入点方法运行后运行
    • @Around:前后都运行
      • 环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知
      • 通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行
      • 对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果接收返回值,必须设定为Object类型
      • 原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成void,也可以设置成Object
      • 由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须抛出Throwable对象
    • @AfterReturning:正常执行完毕后运行
    • @AfterThrowing:抛出异常后运行
  • 切入点表达式标准格式

    execution (public User com.UserService.findById(int))
    
    1. 动作关键字:描述切入点的行为动作,例如execution:表示执行到指定切入点
    2. 访问修饰符:public,private等,可以省略
    3. 返回值:使用 * 匹配任意类型
    4. 目标类/接口(需要完整包名):省略时匹配任意类型, .. 匹配包及其子包的所有类,*
    5. 方法名:使用 * 表示通配符以匹配任意方法, xxx* 匹配名称以 xxx 开头的方法
    6. 参数:可以匹配多个,(..) 匹配有任意数量参数的方法,括号内 * 表示任意类型属性
    7. 异常名:方法定义中抛出指定异常,省略时匹配任意类型
  • 类型匹配语法

    • +:匹配任何数量字符;
    • ..:匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数。
    • +:匹配指定类型的子类型**;仅能作为后缀放在类型模式后边。
  • 示例

    • java.lang.String 匹配String类型;
    • java.*.String 匹配java包下的任何“一级子包”下的String类型;
    • 如匹配java.lang.String,但不匹配java.lang.ss.String
    • java..* 匹配java包及任何子包下的任何类型;
    • java.lang.*ing 匹配任何java.lang包下的以ing结尾的类型;
    • java.lang.Number+ 匹配java.lang包下的任何Number的子类型;

获取通知数据

  • 获取切入点方法的参数

    • JoinPoint:适用于前置、后置、返回后、抛出异常后通知

    • ProceedJointPoint:适用于环绕通知

      //public class UserImpl
      public String test2(int id, String pwd) {
          System.out.println("test2");
          return id+pwd;
      }
      
      //public class MyAdvice
      @Pointcut("execution(* spring.configTest.UserImpl.test2(..))")
      private void pt(){}
      
      @Around("pt()")
      public Object around(ProceedingJoinPoint pjp) throws Throwable{
          Object[] args = pjp.getArgs();
          System.out.println("around");
          System.out.println(Arrays.toString(args));
          return pjp.proceed(args);
      }
      
      @After("pt()")
      public void after(JoinPoint jp) throws Throwable{
          Object[] args = jp.getArgs();
          System.out.println("after");
          System.out.println(Arrays.toString(args));
      }
      
      >>around
      >>[10, 22]
      >>test2
      >>after
      >>[10, 22]
      >>update
      
  • 获取切入点方法返回值

    • 返回后通知

    • 环绕通知

    • 例(test2函数与上文一致)

      //public class MyAdvice
      //returning = 的意思是,如果此函数有返回值,则将返回值填入ret中
      @Pointcut("execution(* spring.configTest.UserImpl.test2(..))")
      private void pt(){
      }
      
      @AfterReturning(value = "pt()",returning = "ret")
      public void afterReturning(Object ret){
          System.out.println(ret);
      }
      
      >>test2
      >>1022
      
  • 获取切入点方法运行异常信息(了解即可)

    • 抛出异常后通知
    • 环绕通知
posted @   ---Wg---  阅读(57)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示