Bootstrap

Spring |(七)AOP概念及工作流程

学习来源黑马程序员SSM框架教程_Spring+SpringMVC+Maven高级+SpringBoot+MyBatisPlus企业实用开发技术

📚AOP简介

  • 一句话描述:AOP是在不改原有代码的前提下对其进行增强。

  • AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构。

  • 以下述代码为例,计算万次执行消耗时间的代码只有save有,但最后deleteupdate方法最终都执行了万次,这里就用到了AOP——在不惊动(改动)原有设计(代码)的前提下,想给谁添加功能就给谁添加。

    @Repository
    public class BookDaoImpl implements BookDao {
        public void save() {
            //记录程序当前执行执行(开始时间)
            Long startTime = System.currentTimeMillis();
            //业务执行万次
            for (int i = 0;i<10000;i++) {
                System.out.println("book dao save ...");
            }
            //记录程序当前执行时间(结束时间)
            Long endTime = System.currentTimeMillis();
            //计算时间差
            Long totalTime = endTime-startTime;
            //输出信息
            System.out.println("执行万次消耗时间:" + totalTime + "ms");
        }
        public void update(){
            System.out.println("book dao update ...");
        }
        public void delete(){
            System.out.println("book dao delete ...");
        }
        public void select(){
            System.out.println("book dao select ...");
        }
    }
    

    在这里插入图片描述

    • 对于上面的案例中BookServiceImpl中有saveupdatedeleteselect方法,这些方法叫连接点
    • 在BookServiceImpl的四个方法中,updatedelete只有打印没有计算万次执行消耗时间,但是在运行的时候已经有该功能,那也就是说updatedelete方法都已经被增强,所以对于需要增强的方法我们叫切入点
    • 执行BookServiceImpl的update和delete方法的时候都被添加了一个计算万次执行消耗时间的功能,将这个功能抽取到一个方法中,换句话说就是存放共性功能的方法,我们叫通知
    • 通知是要增强的内容,会有多个,切入点是需要被增强的方法,也会有多个,那哪个切入点需要添加哪个通知,就需要提前将它们之间的关系描述清楚,那么对于通知和切入点之间的关系描述,我们叫切面
    • 通知是一个方法,方法不能独立存在需要被写在一个类中,这个类叫通知类
      在这里插入图片描述
  • 归纳一下

    • 连接点(JoinPoint):程序执行过程中的任意位置,粒度为执行方法、抛出异常等。
    • 切入点(Pointcut):匹配连接点的式子。
    • 通知(Advice):在切入点处执行的操作,也就是共性功能
    • 通知类:定义通知的类。
    • 切面(Aspect):描述通知与切入点的对应关系。

📚AOP入门案例

🐇环境准备

在这里插入图片描述

  • 创建一个Maven项目。

  • pom.xml添加Spring依赖。

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.10.RELEASE</version>
        </dependency>
    </dependencies>
    
  • 添加BookDao和BookDaoImpl类。

    public interface BookDao {
       public void save();
       public void update();
    }
    
    @Repository
    public class BookDaoImpl implements BookDao {
    
       public void save() {
           System.out.println(System.currentTimeMillis());
           System.out.println("book dao save ...");
       }
    
       public void update(){
           System.out.println("book dao update ...");
       }
    }
    
  • 创建Spring的配置类

    @Configuration
    @ComponentScan("com.itheima")
    public class SpringConfig {
    }
    
  • 编写App运行类

    public class App {
        public static void main(String[] args) {
            ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
            BookDao bookDao = ctx.getBean(BookDao.class);
            bookDao.save();
        }
    }
    

需求:使用SpringAOP的注解方式完成在方法执行前打印出当前系统时间。

  • 目前打印save方法的时候,因为方法中有打印系统时间,所以运行的时候是可以看到系统时间。
  • update就没有该功能。我们要使用SpringAOP的方式在不改变update方法的前提下让其具有打印系统时间的功能。

🐇AOP实现步骤

  • 步骤1:pom.xml添加依赖。spring-context中已经导入了spring-aop,所以不需要再单独导入spring-aop。导入AspectJ的jar包,AspectJ是AOP思想的一个具体实现。
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.4</version>
    </dependency>
    
  • 步骤2:定义接口与实现类。这里沿用环境准备的部分,不作修改。
  • 步骤3:定义通知类和通知。通知即将共性功能抽取出来后形成的方法,共性功能指当前系统时间的打印。
    //类名和方法名任意
    public class MyAdvice {
        public void method(){
            System.out.println(System.currentTimeMillis());
        }
    }
    
  • 步骤4:定义切入点。切入点定义依托一个不具有实际意义的方法进行,即无参数、无返回值、方法体无实际逻辑。
    ublic class MyAdvice {
        @Pointcut("execution(void com.itheima.dao.BookDao.update())")
        private void pt(){}
        public void method(){
            System.out.println(System.currentTimeMillis());
        }
    }
    
  • 步骤5:制作切面。绑定切入点与通知关系,并指定通知添加到原始连接点的具体执行位置
    public class MyAdvice {
        @Pointcut("execution(void com.itheima.dao.BookDao.update())")
        private void pt(){}
    
        @Before("pt()")
        public void method(){
            System.out.println(System.currentTimeMillis());
        }
    }
    
    在这里插入图片描述
  • 步骤6:将通知类配给给容器并标识其为切面类。
    @Component
    @Aspect
    public class MyAdvice {
        @Pointcut("execution(void com.itheima.dao.BookDao.update())")
        private void pt(){}
    
        @Before("pt()")
        public void method(){
            System.out.println(System.currentTimeMillis());
        }
    }
    
  • 步骤7:开启注解格式AOP功能。
    @Configuration
    @ComponentScan("com.itheima")
    @EnableAspectJAutoProxy
    public class SpringConfig {
    }
    
  • 步骤8:运行程序。在执行update方法之前打印了系统时间戳,说明对原始方法进行了增强,AOP编程成功。
    public class App {
        public static void main(String[] args) {
            ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
            BookDao bookDao = ctx.getBean(BookDao.class);
            bookDao.update();
        }
    }
    
    在这里插入图片描述

📚AOP工作流程

  • 流程1:Spring容器启动。
    • 容器启动就需要去加载bean,去加载那些需要被增强的类,如BookServiceImpl,通知类,如MyAdvice。
    • 注意此时bean对象还没有创建成功。
  • 流程2:读取所有切面配置中的切入点。
    • 下面这个例子中有两个切入点的配置。
    • 但第一个ptx()并没有被使用,所以不会被读取。
      在这里插入图片描述
  • 流程3:初始化bean,判定bean对应的类中的方法是否匹配到任意切入点。
    • 第1步在容器启动的时候,bean对象还没有被创建成功。
    • 要被实例化bean对象的类中的方法和切入点进行匹配。
      在这里插入图片描述
      • 匹配失败,创建原始对象,如UserDao。匹配失败说明不需要增强,直接调用原始对象的方法即可。
      • 匹配成功,创建原始对象(目标对象)代理对象,如BookDao
        • 匹配成功说明需要对其进行增强。对哪个类做增强,这个类对应的对象就叫做目标对象。
        • 因为要对目标对象进行功能增强,采用的技术是动态代理,所以会为其创建一个代理对象。
        • 最终运行的是代理对象的方法,在该方法中会对原始方法进行功能增强。
  • 流程4:获取bean执行方法。
    • 获取的bean是原始对象时,调用方法并执行,完成操作。
    • 获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作。

目标对象(Target):原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作的。
代理(Proxy):目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现。

📚小结

  • 知识点1:@EnableAspectJAutoProxy
    在这里插入图片描述
  • 知识点2:@Aspect
    在这里插入图片描述
  • 知识点3:@Pointcut
    在这里插入图片描述
  • 知识点4:@Before
    在这里插入图片描述
;