AOP概念

AOP的全名是aspect-oriented programming面向切面编程,它是面对对象编程(OOP)的一种方式

这个AOP的思想主要是指对一个事务的集中处理.将多个类都要完成的功能都在一个类中统一完成.如用户登录的校验功能,每一个页面其实都要进行这个校验,AOP技术就是单独在一个另外的类中统一的进行校验

AOP技术是一种思想,Spring AOP是这个思想的一个技术实现

AOP主要完成的事务

AOP主要可以完成以下几种事务:

  1. 统一日志记录
  2. 统一方法执行时间统计
  3. 统一的方法返回结果设置
  4. 统一的异常处理
  5. 事务的开启和提交

组成

切面Aspect

切面是由切点和通知组成的.

主要就是一个包含了一些实现功能的AOP集合类,不仅包括了切点和通知,还包含了连接点的定义

连接点 Join Point

程序中调用切面的一个位置,叫做连接点,这个连接点可以是方法调用,可以是抛出异常时,可以是修改参数时

切点 Pointcut

Pointcut 的作⽤就是提供⼀组规则(使⽤ AspectJ pointcut expression language 来描述)来匹 配 Join Point,给满⾜规则的 Join Point 添加 Advice。

切点相当于保存了众多连接点的⼀个集合(如果把切点看成⼀个表,⽽连接点就是表中⼀条⼀条 的数据

通配符

AspectJ ⽀持三种通配符

  • * 匹配任意字符,只匹配⼀个元素(包,类,或⽅法,⽅法参数)
    ..匹配任意字符,可以匹配多个元素 ,在表示类时,必须和 * 联合使⽤。
  • + 表示按照类型匹配指定类的所有类,必须跟在类名后⾯,如 com.cad.Car+ ,表示继承该类的 所有⼦类包括本身

切点表达式:
切点表达式由切点函数组成,其中 execution() 是最常⽤的切点函数,⽤来匹配⽅法,语法为:

execution(<修饰符><返回类型><包.类.⽅法(参数)><异常>)

修饰符,异常一般省略
包,类一般不省略
返回类型,方法名,参数不可以省略

通知 Advice

切点的方法体的实现就是通知.

Spring AOP中有以下几种通知:

前置通知@Before

通知方法会在目标方法之前进行执行

后置通知@After

通知方法在目标方法返回之后或者异常抛出之后

返回之后通知@AfterReturning

通知方法在目标方法返回之后通知

抛异常后通知@AfterThrowing

通知方法在目标方法抛出异常之后执行

环绕通知@Around

通知方法在目标方法执行之前和执行之后都通知

@Aspect//加上Aspect就表示它是一个切面  
@Component  
public class UserAspect {  
  
    @Autowired  
    UserController userController;  
  
    //定义切点,并设置切点的范围  
    @Pointcut("execution(* com.example.demo.controller.UserController.*(..))")  
    public void pointcut(){}//切点没有方法体  
  
    @Before("pointcut()")  
    public void doBefore(){  
        System.out.println("执行before方法");  
    }  
  
    @After("pointcut()")  
    public void doAfter(){  
        System.out.println("执行after方法");  
    }  
  
    @AfterReturning("pointcut()")  
    public void doAfterReturning(){  
        System.out.println("执行AfterReturning");  
    }  
  
    @AfterThrowing("pointcut()")  
    public void doAfterThrow(){  
        System.out.println("执行AfterThrow");  
    }  
  
    public Object doAround(ProceedingJoinPoint joinPoint){  
        Object object=null;  
        System.out.println("执行around方法之前");  
        try {  
            object=joinPoint.proceed();  
        } catch (Throwable throwable) {  
            throwable.printStackTrace();  
        }  
        System.out.println("执行around方法之后");  
        return object;  
    }  
  
}

当有异常抛出时,如果同时有上面的这些通知,那么这些通知的顺序是什么呢?

执行before方法
执行AfterThrow
执行after方法

先执Before通知,在执行抛出异常通知,最后执行After通知.
因为程序没有正常的结束,所以不会执行AfterReturn通知

这也就说明了:

after通知无论如何都会执行,但是afterReturn在遇到异常之后不会正常执行,这个是两者的区别


对于Around通知来说,如果加上了它,这些通知的执行顺序是什么呢?

执行around方法之前
执行before方法
目标文件执行
执行AfterReturning
执行after方法
执行around方法之后
  1. 环绕通知的优先级最高
  2. 接着是前置通知
  3. 执行目标函数
  4. 执行afterReturn通知
  5. 执行后置通知
  6. 最后是有环绕通知结尾

也就是说,环绕通知最先出现,最晚离开
在正常执行的情况下,afterReturn出现在后置通知之前


使用around注解完成执行时间的统计

使用spring提供的stopWatch和joinPoint来进行统计时间

@Around("pointcut()")  
public Object doAround(ProceedingJoinPoint joinPoint){  
    Object object=null;  
    StopWatch stopWatch=new StopWatch();  
    System.out.println("执行around方法之前");  
    try {  
        stopWatch.start();  
        object=joinPoint.proceed();  
        stopWatch.stop();  
    } catch (Throwable throwable) {  
        throwable.printStackTrace();  
    }finally {  
        System.out.println(joinPoint.getSignature().getDeclaringType()+"."+  
                            joinPoint.getSignature().getName()+"执行了"+stopWatch.getTotalTimeMillis()+"ms");  
    }  
  
    System.out.println("执行around方法之后");  
    return object;  
}

四大组成的联系

spring aop 切面含参数 注解_java


spring aop 切面含参数 注解_java_02

Spring AOP的实现

主要分为以下四个步骤:

一.创建SpringBoot AOP框架

还是在pom.xml中引入SPringBoot框架的AOP依赖,我们可以去Maven中央仓库中找到这个依赖,然后赋值到项目的pom.xml中.

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    <version>2.7.0</version>
</dependency>

记住,一定时SpringBoot的依赖,不要添加成Spring的依赖了,因为现在大多数的项目都是使用SpringBoot较多hh

二.创建切面

切面是一个类,在类的前面加上注解@Aspect就表示这是一个切面类.

同时,它也要加上五大类注解@Component

@Aspect//加上Aspect就表示它是一个切面  
@Component  
public class UserAspect {  
    
}

三.创建切点

在函数的前面加上@Pointcut表示是一个切点.

@Pointcut的后面是指定该切点函数的范围.

//定义切点,并设置切点的范围  
    @Pointcut("execution(* com.example.demo.controller.UserController.*(..))")  
    public void pointcut(){}//切点没有方法体

括号里面的内容是AspectJ语言,
execution(返回类型 包名 类名 方法名 参数类型)

execution(*(返回类型) com.example.demo.controller(包名).UserController(类名).*(方法名)(..)(参数类型))

四.实现通知

直接在切面类中,创建通知函数就可以,在方法的前面加上通知类型.

通知类型的括号里面写通知对应的切点是谁.

@Before("pointcut()")  
    public void UserLoginOrNot(){  
        System.out.println("执行before方法");  
    }

整体代码:

@Aspect//加上Aspect就表示它是一个切面  
@Component  
public class UserAspect {  
  
    @Autowired  
    UserController userController;  
  
    //定义切点,并设置切点的范围  
    @Pointcut("execution(* com.example.demo.controller.UserController.*(..))")  
    public void pointcut(){}//切点没有方法体  
  
    @Before("pointcut()")  
    public void UserLoginOrNot(){  
        System.out.println("执行before方法");  
    }  
}

实现原理

SpringAOP是基于动态代理的方式实现的.

在没有AOP之前,前端直接和后端进行交互,但是在引入了AOP之后,要先经过AOP的一些通知,这就是动态代理.

spring aop 切面含参数 注解_AOP_03

主要有两种原理实现:

  1. JDK proxy
  2. CGLIB proxy

这两个原理相对来说,大部分时间还是使用CGLIB proxy的,因为它的效率高,当CGLIB无法完成的时候再使用JDK proxy

CGLIB是通过继承目标类来实现动态代理的,对于哪些不可以被继承的类(被final修饰的类) 我们就只好使用JDK proxy

织入

织入是创建新的代理对象的过程,切⾯在指定的连接点被织⼊到⽬标对 象中。 在⽬标对象的⽣命周期⾥有多个点可以进⾏织⼊:

编译期:切⾯在⽬标类编译时被织⼊。这种⽅式需要特殊的编译器。AspectJ的织⼊编译器就 是以这种⽅式织⼊切⾯的。

类加载器:切⾯在⽬标类加载到JVM时被织⼊。这种⽅式需要特殊的类加载器 (ClassLoader),它可以在⽬标类被引⼊应⽤之前增强该⽬标类的字节码。AspectJ5的加载 时织⼊(load-time weaving. LTW)就⽀持以这种⽅式织⼊切⾯。

运⾏期:切⾯在应⽤运⾏的某⼀时刻被织⼊。⼀般情况下,在织⼊切⾯时,AOP容器会为⽬标对象动态创建⼀个代理对象。SpringAOP就是以这种⽅式织⼊切⾯的。

这两种方法都是在运行时发挥动态代理的