一、AOP 简介

1. 什么是 AOP

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

1. AOP 的作用

AOP是在不改原有代码的前提下对其进行增强。

3. AOP 核心概念

spring aop多个切面配置在service方法上不生效 spring aop切面切点_抛出异常


(1)连接点(JoinPoint):程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等。在SpringAOP中,理解为方法的执行。

(2)切入点(Pointcut):匹配连接点的式子。在 SpringAOP 中,一个切入点可以只描述一个具体方法,也可以匹配多个方法。

1️⃣一个具体方法:【com.itheima.dao】包下的 BookDao 接口中的无形参无返回值的 save 方法

2️⃣匹配多个方法:所有的 save 方法,所有的 get 开头的方法,所有以 Dao 结尾的接口中的任意方法,所有带有一个参数的方法

(3)通知(Advice):在切入点处执行的操作,也就是共性功能。在 SpringAOP 中,功能最终以方法的形式呈现

(4)通知类:定义通知的类

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

二、AOP入门案例

1. 思路分析

(1)导入坐标(pom.xml)
(2)制作连接点方法(原始操作,Dao接口与实现类)
(3)制作共性功能(通知类与通知)
(4)定义切入点
(5)绑定切入点与通知关系

2. 实现步骤

(1)添加依赖

<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>

(2)定义接口与实现类


(3)定义通知类和通知

通知就是将共性功能抽取出来后形成的方法,共性功能指的就是当前系统时间的打印。

(4)定义切入点


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

(5)制作切面


切面是用来描述通知和切入点之间的关系。绑定切入点与通知关系,并指定通知添加到原始连接点的具体执行位置



@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()); } }





spring aop多个切面配置在service方法上不生效 spring aop切面切点_抛出异常_02

三、AOP 工作流程

1. AOP 工作流程

(1)Spring 容器启动

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

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

1️⃣匹配失败,创建对象
2️⃣匹配成功,创建原始对象(目标对象)的代理对象

(4)获取bean执行方法

1️⃣获取bean,调用方法并执行,完成操作
2️⃣获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作

2. AOP核心概念

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

四、AOP 配置管理

1. AOP切入点表达式

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

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

1.1 语法格式

对于切入点的描述:

(1)描述方式一:执行【 com.itheima.dao】 包下的  BookDao  接口中的无参数  update  方法


execution(void com.itheima.dao.BookDao.update())

(2)描述方式二:执行【com.itheima.dao.impl】包下的 BookDaoImpl 类中的无参数 update 方法

execution(void com.itheima.dao.impl.BookDaoImpl.update())


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


名)

spring aop多个切面配置在service方法上不生效 spring aop切面切点_AOP_03

 


1.2 通配符

使用通配符描述切入点,主要的目的就是简化之前的配置

spring aop多个切面配置在service方法上不生效 spring aop切面切点_System_04

 

1.3 书写技巧


2. AOP通知类型

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


共提供了 5 种通知类型:

(1)前置通知

(2)后置通知

(3) 环绕通知 ( 重点 )

(4)返回后通知 ( 了解 )

(5)抛出异常后通知 ( 了解 )

spring aop多个切面配置在service方法上不生效 spring aop切面切点_AOP_05

 



通知类:


@Component @Aspect public class MyAdvice { @Pointcut("execution(void com.itheima.dao.BookDao.update())") private void pt() { } @Before("pt()") public void before() { System.out.println("before advice ..."); } @After("pt()") public void after() { System.out.println("after advice ..."); } @Around("pt()") public Object around(ProceedingJoinPoint pjp) throws Throwable { //获取执行签名信息 Signature signature = pjp.getSignature(); //通过签名获取执行类型(接口名) String className = signature.getDeclaringTypeName(); String methodName = signature.getName(); System.out.println("around before advice ..."); //通过签名获取执行操作名称(方法名) Object ret = pjp.proceed(); System.out.println("around after advice ..."); return ret; } @AfterReturning("pt()") public void afterReturning() { System.out.println("afterReturning advice ..."); } @AfterThrowing("pt()") public void afterThrowing() { System.out.println("afterThrowing advice ..."); } }







spring aop多个切面配置在service方法上不生效 spring aop切面切点_抛出异常_06


环绕通知注意事项

(1) 环绕通知必须依赖形参  ProceedingJoinPoint  才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知

(2)通知中如果未使用  ProceedingJoinPoint  对原始方法进行调用将跳过原始方法的执行

(3)对原始方法的调用可以不接收返回值,通知方法设置成 void 即可,如果接收返回值,最好设定为 Object 类型

(5) 原始方法的返回值如果是  void  类型,通知方法的返回值类型可以设置成  void, 也可以设置成 Object

(5) 由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须要处理  Throwable  异常

3. AOP通知获取数据

(1)获取切入点方法的参数,所有的通知类型都可以获取参数

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

2️⃣ProceedingJoinPoint :适用于环绕通知

(2)获取切入点方法返回值,前置和抛出异常后通知是没有返回值,后置通知可有可无,所以不做研究

1️⃣返回后通知

2️⃣环绕通知

(3)获取切入点方法运行异常信息,前置和返回后通知是不会有,后置通知可有可无,所以不做研究

1️⃣抛出异常后通知

2️⃣环绕通知

编写通知类:

@Component@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
    private void pt() {
    }

    @Before("pt()")
    public void before(JoinPoint jp) {
        Object[] args = jp.getArgs();
        System.out.println(Arrays.toString(args));
        System.out.println("before advice ...");
    }

    @After("pt()")
    public void after(JoinPoint jp) {
        Object[] args = jp.getArgs();
        System.out.println(Arrays.toString(args));
        System.out.println("after advice ...");
    }

    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object[] args = pjp.getArgs();
        System.out.println(Arrays.toString(args));
        System.out.println("around before advice ...");
        //通过签名获取执行操作名称(方法名)
        Object ret = pjp.proceed(args);
        System.out.println("around after advice ...");
        return ret;
    }

    @AfterReturning(value = "pt()", returning = "ret")
    public void afterReturning(Object ret) {
        //ret是返回值
        System.out.println("afterReturning advice ..." + ret);
    }

    @AfterThrowing(value = "pt()", throwing = "t")
    public void afterThrowing(Throwable t) {
        System.out.println("afterThrowing advice ..." + t);
    }

}