文章目录
- 📚AOP简介
- 📚AOP入门案例
- 🐇环境准备
- 🐇AOP实现步骤
- 📚AOP工作流程
- 📚小结
学习来源:黑马程序员SSM框架教程_Spring+SpringMVC+Maven高级+SpringBoot+MyBatisPlus企业实用开发技术
📚AOP简介
-
一句话描述:AOP是在不改原有代码的前提下对其进行增强。
-
AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构。
-
以下述代码为例,计算万次执行消耗时间的代码只有
save
有,但最后delete
和update
方法最终都执行了万次,这里就用到了AOP——在不惊动(改动)原有设计(代码)的前提下,想给谁添加功能就给谁添加。java">@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中有
save
,update
,delete
和select
方法,这些方法叫连接点。 - 在BookServiceImpl的四个方法中,
update
和delete
只有打印没有计算万次执行消耗时间,但是在运行的时候已经有该功能,那也就是说update
和delete
方法都已经被增强,所以对于需要增强的方法我们叫切入点。 - 执行BookServiceImpl的update和delete方法的时候都被添加了一个计算万次执行消耗时间的功能,将这个功能抽取到一个方法中,换句话说就是存放共性功能的方法,我们叫通知。
- 通知是要增强的内容,会有多个,切入点是需要被增强的方法,也会有多个,那哪个切入点需要添加哪个通知,就需要提前将它们之间的关系描述清楚,那么对于通知和切入点之间的关系描述,我们叫切面。
- 通知是一个方法,方法不能独立存在需要被写在一个类中,这个类叫通知类。
- 对于上面的案例中BookServiceImpl中有
-
归纳一下:
- 连接点(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类。
java">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的配置类
java">@Configuration @ComponentScan("com.itheima") public class SpringConfig { }
-
编写App运行类
java">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:定义通知类和通知。通知即将共性功能抽取出来后形成的方法,共性功能指当前系统时间的打印。
java">//类名和方法名任意 public class MyAdvice {public void method(){System.out.println(System.currentTimeMillis());} }
- 步骤4:定义切入点。切入点定义依托一个不具有实际意义的方法进行,即无参数、无返回值、方法体无实际逻辑。
java">ublic class MyAdvice {@Pointcut("execution(void com.itheima.dao.BookDao.update())")private void pt(){}public void method(){System.out.println(System.currentTimeMillis());} }
- 步骤5:制作切面。绑定切入点与通知关系,并指定通知添加到原始连接点的具体执行位置。
java">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:将通知类配给给容器并标识其为切面类。
java">@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功能。
java">@Configuration @ComponentScan("com.itheima") @EnableAspectJAutoProxy public class SpringConfig { }
- 步骤8:运行程序。在执行update方法之前打印了系统时间戳,说明对原始方法进行了增强,AOP编程成功。
java">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