Java EE --- Spring AOP

1. 什么是 Spring AOP

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

例如, 在没有学习AOP之前, 之前的判断当前登录状态, 就需要在每一个页面都实现登录校验, 在有了AOP之后, 外面只需在某一处配置以下, 所有的页面就都可以实现登录验证了, 就不需要写太多重复的代码,

Spring AOP, 是一个框架, 提高了一种对 AOP 思想的实现.

2. AOP 的组成

在这里插入图片描述

2.1 切面 (Aspect)

切面由切点和通知组成, 它既包含了横切逻辑的定义, 也包括了连接点的定义.

切面是包含了: 通知, 切点和切面的类, 相当于 AOP 实现的某个功能的集合

2.2 切点 (Pointcur)

切点的作用就是提供一组规则 (使用 AspectJ pointcut expression language 来描述) 来匹配 连接点, 给满足规则的 连接点添加 Advice

切点相当于保存了众多连接点的一个集合

2.3 连接点 (Join Point)

应用执行过程中能够插入切面的一个点, 这个点可以是方法的调用时, 抛出异常时, 甚至修改字段时. 切面代码可以利用这些点插入到应用的正常流程之中, 并添加新的行为.

扫描二维码关注公众号,回复: 14276142 查看本文章

连接点相当于需要被增强的某个 AOP 功能的所有方法.

2.4 通知 (Advice)

定义了切面是什么, 何时使用, 其描述切面要完成的工作, 还解决何时执行这个工作的问题,
Spring切面类中, 可以在方法上使用以下注解, 会设置方法为通知方法, 在满足条件后会通知本方法进行调用.
⽅法进⾏调用:
前置通知使用 @Before:通知方法会在目标方法调用之前执行.
后置通知使用 @After:通知方法会在目标方法返回或者抛出异常后调用.
返回之后通知使用 @AfterReturning:通知方法会在目标方法返回后调用.
抛异常后通知使用 @AfterThrowing:通知方法会在目标方法抛出异常后调用.
环绕通知使用 @Around:通知包裹了被通知的方法, 在被通知的方法通知之前和调用之后执行自定义的行为.

3. Spring AOP 的使用

3.1 添加 AOP 框架

在 pom.xml 中添加依赖

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

3.2 定义切面和切点

@Aspect // 定义切面
@Component
public class UserAspect {
    
    
    // 切点 (配置拦截规则)
    @Pointcut("execution(* com.example.demo.controller.UserController.*)")
    public void pointcut() {
    
    
        // 这是一个空方法, 不需要有具体的实现
    }
}

切点表达式注意事项

AspectJ 支持三种通配符

  • * : 匹配任意字符, 只匹配一个元素 (包, 类, 方法, 方法参数)
  • .. : 匹配任意字符,可以匹配多个元素, 在标识类时, 必须联合 * 使用
  • + : 表示按类型匹配指定类和所有类, 必须跟在类名后面, 如 com.cad.Car+, 表示继承该类的所有子类包括本身

execution() 是最常用的切点函数
语法为: execution(<修饰符> <返回类型> <包.类.方法(参数)> <异常>) (注意: 修饰符和异常可以省略)
示例:
execution(* com.cad.demo.User.*(..)) :匹配 User 类⾥的所有⽅法。
execution(* com.cad.demo.User+.*(..)) :匹配该类的⼦类包括该类的所有⽅法。
execution(* com.cad.*.*(..)) :匹配 com.cad 包下的所有类的所有⽅法。
execution(* com.cad..*.*(..)) :匹配 com.cad 包下、⼦孙包下所有类的所有⽅法。
execution(* addUser(String, int)) :匹配 addUser ⽅法,且第⼀个参数类型是 String,第⼆个参数类型是 int。

3.3 定义通知 (五种)


@Aspect // 定义切面
@Component
public class UserAspect {
    
    
    // 切点 (配置拦截规则)
    @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 doAfterThrowing() {
    
    
        System.out.println("执行 AfterThrowing 方法");
    }

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

正常时
在这里插入图片描述
抛出异常时
在这里插入图片描述

4. Spring AOP 实现原理

Spring AOP 是构建在动态代理基础上, 因此 Spring 对 AOP 的支持局限于方法级别的拦截.

Spring AOP 是基于动态代理实现的.
 
动态代理分为两类:

  1. JDK Proxy(JDK 动态代理机制)
  2. CGLIB 动态代理

 
默认情况下, 实现了接口的类, 使用 AOP 会基于 JDK 生成代理类, 没有实现接口的类, 会基于 CGLIB 生成代理类

4.1 织入 (Weaving)

代理的生成时机
织⼊是把切面应用到目标对象并创建新的代理对象的过程,切面在指定的连接点被织⼊到目标对象中。

在目标对象的⽣命周期里有多个点可以进⾏织入:
 
编译期:切面在目标类编译时被织⼊。这种⽅式需要特殊的编译器。AspectJ的织⼊编译器就是以这种方式织入切⾯的。
 
类加载器:切⾯在目标类加载到JVM时被织入。这种方式需要特殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5的加载时织入(load-time weaving. LTW)就支持以这种方式织入切面。
 
运行期:切面在应用运行的某⼀时刻被织入。⼀般情况下,在织入切面时,AOP容器会为目标对象动态创建一个代理对象. Spring AOP 就是以这种方式织入切面的

4.2 JDK 和 CGLIB 实现的区别

  1. JDK 实现要求被代理类必须实现接口, 之后是通过 InvocationHandlerProxy, 在运行时动态的在内存中生成了代理类对象, 该代理对象是通过实现同样的接口实现 (类似静态代理接口实现的方式), 只是该代理类是在运行期时,动态的织入统一的业务逻辑字节码来完成.
  2. CGLIB 实现, 被代理类可以不实现接口, 是通过继承被代理类, 在运行时动态的生成代理类对象.

猜你喜欢

转载自blog.csdn.net/wwzzzzzzzzzzzzz/article/details/125257500