aspect初识

Aspect初识

  概述

  Spring有两个核心的概念,一个是IOC/DI,一个是AOP,通过这篇文章我们来认识下AOP。

  一、什么是AOP?

   AOP的全称是Aspect Oriented Programming,即面向切面编程。是实现功能统一维护的一种技术,它将业务逻辑的各个部分进行隔离,使开发人员在编写业务逻辑时可以专心于核心业务,从而提高了开发效率。

   作用:在不修改源码的基础上,对已有方法进行增强

   实现原理:动态代理技术

   优势:减少重复代码、提高开发效率、维护方便

   应用场景:事务处理、日志管理、权限控制、异常处理等方面。

  二、AOP和OOP的区别?

  我们都知道OOP是一种编程思想,那么AOP也是一种编程思想,编程思想主要的内容就是指导程序员该如何编写程序,所以它们两个是不同的编程范式。

  AOP 是 OOP(Object Oriented Programming-面向对象编程)的一种延续,二者互补,并不对立。

  AOP 的目的是将横切关注点(如日志记录、事务管理、权限控制、接口限流、接口幂等等)从核心业务逻辑中分离出来,通过动态代理、字节码操作等技术,实现代码的复用和解耦,提高代码的可维护性和可扩展性。

  OOP 的目的是将业务逻辑按照对象的属性和行为进行封装,通过类、对象、继承、多态等概念,实现代码的模块化和层次化(也能实现代码的复用),提高代码的可读性和可维护性。

  三、AOP的核心概念

  涉及到:连接点、切点、通知、通知类、切面

  1. 连接点(JoinPoint):指能被拦截到的点,在Spring中只有方法能被拦截

  2. 切点(Pointcut):指要对那些连接点进行拦截,即被增强的方法

  3. 通知(Advice):指拦截后要做的事情,即切点被拦截后执行的方法

  4. 通知类定义通知的类

  5. 切面(Aspect):描述通知与切点的对应关系

  6. 织入(Weaving):织入是将增强添加到目标类具体连接点上的过程,

  AOP有三种织入方式:

  ① 编译期织入:需要特殊的Java编译器(例如AspectJ的ajc);

  ②装载期织入:要求使用特殊的类加载器,在装载类的时候对类进行增强;

  ③运行时织入:在运行时为目标类生成代理实现增强。Spring采用了动态代理的方式实现了运行时织入,而AspectJ采用了编译期织入和装载期织入的方式 

  四、AOP实现步骤

  1.  添加依赖

  pom.xml

1 <dependency>
2 <groupId>org.aspectj</groupId>
3 <artifactId>aspectjweaver</artifactId>
4 <version>1.9.4</version>
5 </dependency>

  2.  定义接口与实现类

  环境准备的时候,BookDaoImpl已经准备好,不需要做任何修改

  3.  定义通知类和通知

  通知就是切点被拦截后执行的方法,这里我们举例时涉及的拦截后执行的方法就是当前系统时间的打印。

1 public class MyAdvice {
2    public void method(){
3       System.out.println(System.currentTimeMillis());
4    }
5 }

  类名和方法名没有要求,可以任意。

  4.  定义切点

  BookDaoImpl中有两个方法,分别是save和update,我们要增强的是update方法,该如何定义呢?

  示例代码如下:

1 public class MyAdvice {
2    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
3    private void pt(){}
4    public void method(){
5           System.out.println(System.currentTimeMillis());
6        }
7    }

  说明:

  切点定义依托一个不具有实际意义的方法进行,即无参数、无返回值、方法体无实际逻辑

  5.  制作切面

  切面是用来描述通知和切点之间的关系,如何进行关系的绑定?

1 public class MyAdvice {
2     @Pointcut("execution(void com.itheima.dao.BookDao.update())")
3     private void pt(){}
4     
5     @Before("pt()")
6     public void method(){
7         System.out.println(System.currentTimeMillis());
8     }
9 }

   绑定切入点与通知关系,并指定通知添加到原始连接点的具体执行位置

   说明:@Before翻译过来是之前,也就是说通知会在切点方法执行之前执行,除此之前还有其他四种类型,后面会讲。

   6.  将通知类配给容器并标识其为切面类

 1 @Component
 2 @Aspect
 3 public class MyAdvice {
 4     @Pointcut("execution(void com.itheima.dao.BookDao.update())")
 5     private void pt(){}
 6     
 7     @Before("pt()")
 8     public void method(){
 9         System.out.println(System.currentTimeMillis());
10     }
11 }

   7.  开启注解格式AOP功能

1 @Configuration
2 @ComponentScan("com.itheima")
3 @EnableAspectJAutoProxy
4 public class SpringConfig {
5 
6 }

    8.  运行程序

1 public class App {
2     public static void main(String[] args) {
3         ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
4         BookDao bookDao = ctx.getBean(BookDao.class);
5         bookDao.update();
6     }
7 }

   看到在执行update方法之前打印了系统时间戳,说明对原始方法进行了增强,AOP编程成功。

   五、语法表达式

   1.  切点表达式

   切点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数列表) 异常名)

   对于这个格式,我们不需要硬记,通过一个例子,理解它:

   execution(public User com.itheima.service.UserService.findById(int))

   1) 动作关键字: 描述切入点的行为动作,例如 execution 表示执行到指定切入点

   2)访问修饰符:可以是public,private等,可以省略

   3)返回值:比如这里的User,*表示任意类型

   4)包名

   说明:多级包使用点连接,比如这里的com.itheima.service。包名使用*表示任意包,多级包结构要写多个*,使用*..表示任意包结构。

   5)类/接口名称:比如这里的UserService

   6)方法名:比如这里的findById

   7)参数列表 

  • 基本数据类型直接写类型
  • 引用类型写包名.类名 比如java.lang.String
  • *表示匹配一个任意类型参数
  • .. 表示匹配任意类型任意个数的参数

   8)异常名:方法定义中抛出指定异常,可以省略

   9) 全通配:* *..*(..)

  2.  通配符

  我们使用通配符描述切入点,主要的目的就是简化之前的配置,具体都有哪些通配符可以使用?

   *:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现

 execution(public * com.itheima.*.UserService.find*(*)) 

  匹配com.itheima包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法

  ..:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写

 execution(public User com..UserService.findById(..)) 

  匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法

  +:专用于匹配子类类型

 execution(* *..*Service+.*(..)) 

 1 匹配接口,能匹配到
 2 execution(void com.itheima.dao.impl.BookDaoImpl.update())
 3 匹配实现类,能匹配到
 4 execution(* com.itheima.dao.impl.BookDaoImpl.update())
 5 返回值任意,能匹配到
 6 execution(* com.itheima.dao.impl.BookDaoImpl.update(*))
 7 返回值任意,但是update方法必须要有一个参数,无法匹配,要想匹配需要在update接口和实现类添加参数
 8 execution(void com.*.*.*.*.update())
 9 返回值为void,com包下的任意包三层包下的任意类的update方法,匹配到的是实现类,能匹配
10 execution(void com.*.*.*.update())
11 返回值为void,com包下的任意两层包下的任意类的update方法,匹配到的是接口,能匹配
12 execution(void *..update())
13 返回值为void,方法名是update的任意包下的任意类,能匹配
14 execution(* *..*(..))
15 匹配项目中任意类的任意方法,能匹配,但是不建议使用这种方式,影响范围广
16 execution(* *..u*(..))
17 匹配项目中任意包任意类下只要以u开头的方法,update方法能满足,能匹配
18 execution(* *..*e(..))
19 匹配项目中任意包任意类下只要以e结尾的方法,update和save方法能满足,能匹配
20 execution(void com..*())
21 返回值为void,com包下的任意包任意类任意方法,能匹配,*代表的是方法
22 execution(* com.itheima.*.*Service.find*(..))
23 将项目中所有业务层方法的以find开头的方法匹配
24 execution(* com.itheima.*.*Service.save*(..))
25 将项目中所有业务层方法的以save开头的方法匹配

   六、通知类型

   AOP通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时要将其加入到合理的位置。

   那么,通知具体要添加到切入点的哪里? 

   1. AOP有以下5种通知类型

   1.  前置通知(Before): 在方法执行前添加功能

   2. 后置通知(After): 无论方法是否抛出异常,都会执行该通知

   3. 异常通知(AfterThrowing):在方法抛出异常后添加功能

   4. 返回通知(AfterReturning):在方法返回结果之后执行(因此该通知方法在方法抛出异常时,不能执行)

   5. 环绕通知(Around):在方法执行前后添加功能

   2. 注意事项

   1)返回通知和异常通知,只有一个会执行

   2)环绕通知功能比较强大,它可以追加功能到方法执行的前后,这也是比较常用的方式,它可以实现其他四种通知类型的功能

 

posted @ 2023-07-03 10:20  欢乐豆123  阅读(9)  评论(0编辑  收藏  举报