Spring AOP

✏️作者:银河罐头
系列专栏:JavaEE

“种一棵树最好的时间是十年前,其次是现在”

目录

  • 什么是 AOP?
  • AOP 相关概念
    • 切⾯(Aspect)
    • 切点(Pointcut)
    • 通知(Advice)
    • 连接点(Join Point)
  • 实现 Spring AOP
    • 添加 Spring Boot AOP 框架
    • 创建切面
    • 创建切点
    • 创建通知
    • 创建连接点
  • Spring AOP 实现原理
    • 动态代理
    • 织⼊(Weaving):代理的⽣成时机

什么是 AOP?

AOP(Aspect Oriented Programming):⾯向切⾯编程,它是⼀种思想,它是对某一类事情的集中处理

⽐如⽤户登录权限的校验,没学 AOP 之前,我们所有需要判断⽤户登录的⻚⾯(中 的⽅法),都要各⾃实现或调⽤⽤户验证的⽅法,然⽽有了 AOP 之后,我们只需要在某⼀处配 置⼀下,所有需要判断⽤户登录⻚⾯(中的⽅法)就全部可以实现⽤户登录验证了,不再需要每 个⽅法中都写相同的⽤户登录验证了。 ⽽ AOP 是⼀种思想,⽽ Spring AOP 是⼀个框架,提供了⼀种对 AOP 思想的实现,它们的关系和 IoC 与 DI 类似。

除了统⼀的⽤户登录判断之外,AOP 还可以实现:

统⼀⽇志记录

统⼀⽅法执⾏时间统计

统⼀的返回格式设置

统⼀的异常处理

事务的开启和提交等

也就是说使⽤ AOP 可以扩充多个对象的某个能⼒,所以 AOP 可以说是 OOP(Object Oriented Programming,⾯向对象编程)的补充和完善。

AOP 相关概念

切⾯(Aspect)

切⾯(Aspect)由切点(Pointcut)和通知(Advice)组成。

切⾯指的是某一方面的具体内容。

比如用户的登陆判断就是一个"切⾯",而日志的统计记录又是另一个"切面"。

切点(Pointcut)

如果把切面看成是一个类,那么切点就相当于是类里的一个方法。

定义一个"拦截规则"。

以博客系统举例:不是每个页面都需要进行登录校验,比如登录页或注册页就不需要。

通知(Advice)

通知:方法的具体实现,执行 整个 AOP 的逻辑业务

前置通知使⽤ @Before:在目标方法(实际要执行的方法)调用之前执行的通知。

后置通知使⽤ @After:在目标方法调用之后执行的通知。

比如在添加文章(目标方法)之前进行登录校验就是前置通知。在添加文章之后进行登录校验就是后置通知。

环绕通知 @Around:在目标方法执行前、后都执行的通知。

环绕通知使用场景:记录方法的调用时间

异常通知@AfterThrowing:在目标方法抛出异常的时候执行的通知。

异常通知使用场景:声明式事务

返回通知@AfterReturning:在目标方法返回的时候执行的通知。

连接点(Join Point)

所有可能触发"切点"的点就叫做"连接点"。

以博客系统为例:

假设点击发布博客,这是一个"连接点",

此时被拦截,没有立刻跳转到编辑页(“切点”),

而是跳转到登录页进行登录校验(“通知”),

切点 + 通知 = 切面

实现 Spring AOP

添加 Spring Boot AOP 框架

Maven Repository: org.springframework.boot » spring-boot-starter-aop » 2.7.11 (mvnrepository.com)


<dependency>
   <groupId>org.springframework.bootgroupId>
   <artifactId>spring-boot-starter-aopartifactId>
   <version>2.7.11version>
dependency>

image-20230531203003844

创建切面

package com.example.demo.common;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect //切面
@Component //不能省略
public class UserAOP {
    
}

创建切点

@Aspect //切面
@Component //不能省略
public class UserAOP {
    //切点(配置拦截规则)
    @Pointcut("execution(* com.example.demo.controller.UserController.* (..))")
    public void pointcut(){

    }
}

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

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

修饰符和异常可以省略

Spring AOP_第1张图片

创建通知

@Before("pointcut()")
public void doBefore(){
    System.out.println("执行前置通知: " + LocalDateTime.now());
}

创建连接点

package com.example.demo.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {
    @RequestMapping("/user/sayhi")
    public String sayHi(){
        System.out.println("执行了 sayHi 方法");
        return "hi spring boot aop";
    }
    @RequestMapping("/user/login")
    public String login(){
        System.out.println("执行了 login 方法");
        return "do user login";
    }
}
package com.example.demo.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ArticleController {
    @RequestMapping("/article/sayhi")
    public String sayHi(){
        System.out.println("执行了 Article sayHi 方法");
        return "article: hi spring boot aop";
    }
}

Spring AOP_第2张图片

image-20230604141653992

Spring AOP_第3张图片

image-20230604141726098

Spring AOP_第4张图片

image-20230604141840346

在执行 ArticleController 类的方法时没有执行前置通知。

因为之前配置了拦截规则,只拦截 UserController 里的方法。

@After(("pointcut()"))
public void doAfter(){
    System.out.println("执行后置通知: " + LocalDateTime.now());
}

Spring AOP_第5张图片

Spring AOP_第6张图片

Spring AOP_第7张图片

image-20230604142532326

@Around("pointcut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("环绕通知开始");
    Object obj = joinPoint.proceed();
    System.out.println("环绕通知结束");
    return obj;
}

Spring AOP_第8张图片

Spring AOP_第9张图片

  • 前置通知,后置通知,环绕通知先后顺序?

Spring AOP_第10张图片

Spring AOP_第11张图片

Spring AOP 实现原理

动态代理

Spring AOP 是构建在动态代理基础上。

Spring AOP ⽀持 JDK Proxy 和 CGLIB ⽅式实现动态代理。

默认情况下,实现了接⼝的类,使 ⽤ AOP 会基于 JDK ⽣成代理类,没有实现接⼝的类,会基于 CGLIB ⽣成代理类。

Spring AOP_第12张图片

举例:

如果没有代理类,做"添加文章"操作时,就要在"目标对象"内部去实现 "判断登录状态"的操作。而有了代理类之后,就可以在"代理类"内部去实现"判断用户登陆状态"的操作。相当于代理类做了一层筛选。这样一来"目标对象"要做的事就变少了。

织⼊(Weaving):代理的⽣成时机

织⼊是把切⾯应⽤到⽬标对象并创建新的代理对象的过程,切⾯在指定的连接点被织⼊到⽬标对 象中。

在⽬标对象的⽣命周期⾥有多个点可以进⾏织⼊:

编译期:

类加载期:

运⾏期:

1.JDK Proxy:

要求:被代理类一定要实现 接口

底层是:反射实现动态代理。

速度快

2.CGLIB:

通过实现代理类的子类来实现动态代理。所以它不能代理被 final 修饰的类。

底层是:字节码增强技术生成子类。

在 Spring 框架中,既使用了 JDK 动态代理又使用 CGLIB,默认情况下使用的是 JDK 动态代理,但是如果目标对象没有实现接口,则会使用 CGLIB 动态代理。

你可能感兴趣的:(JavaEE进阶,spring,java,后端)