Spring AOP【开发实践】

一、AOP概述

1.1 OOP

OOP 是 Object-Oriented Programming 的缩写,指面向对象编程。
OOP 用于定义从上到下的关系,其核心是封装、继承和多态。

1.2 AOP

AOP 是 Aspect-Oriented Programming 的缩写,指面向切面编程。
AOP 是 OOP 的延续(而非替代),用于定义从左到右的关系。
通过(静态/动态)代理技术,在不修改目标代码的前提下添加额外的功能。

1.3 AOP的优点

模块化,松耦合,提高代码的复用性、可维护性、可读性、灵活性和可扩展性。

1.4 AOP的经典场景

日志记录、事务管理、权限控制(认证与鉴权)、缓存处理、异常处理、资源池管理、同步控制等。

1.5 AOP的常见实现

AspectJ AOP:功能强大,性能较好,基于静态代理实现。
Spring AOP:功能够用,性能相对较差,基于动态代理动态代理实现。主要优点是配置简单和易于在Spring中集成使用。

二、AOP中的核心概念

AOP用于在代码的某些特殊位置添加额外的功能(即增强)。所以有两个关注点,一是位置,二是功能。

2.1 Joint Point(连接点)和 Ponitcut(切点)

Joint Point
连接点指代码中可以增强的某个特定的点,如方法调用、构造函数调用、字段访问、异常处理、类初始化和销毁等。

连接点是可以增强的点,但并非所有连接点都需要去增强,根据需求来对特定的连接点做相应的增强。

Ponitcut
指一组连接点的集合,通过表达式来匹配一组连接点。

使用AOP时,是通过切点来批量对一组连接点做增强的,而不直接在连接点做增强。直接在单个连接点上的增强没有意义,不如直接写在代码里,不需要用AOP。

2.2 Advice(通知)

Advice 指通知,等价于增强方式(位置)和增强逻辑(代码)。

按增强方式分类,有五种通知:

  • 环绕通知(@Around):在调用目标方法之前和之后执行(异常时没有之后执行的部分)。
  • 前置通知(@Before):在目标方法执行之前执行。
  • 返回通知(@AfterReturning):连接点正常结束时的增强(生成返回值之后)。
  • 后置通知(@After):在目标方法正常结束后执行(不论是否异常)。
  • 异常通知(@AfterThrowing):连接点异常结束时的增强。

在Spring4中,环绕通知将前置通知包裹起来(环绕仅环前置通知),之后是后置通知和返回通知/异常通知(假环绕和假后置)。

在Spring5中,环绕通知将其他所有通知包裹起来,内部顺序是前置通知、返回通知/异常通知和后置通知(真环绕和真后置)。

2.3 Aspect(切面)

切面就是切入点和通知的集合。

简单来说,一个切面对应某种增强业务,使用一个类来实现。类中使用切入点定义了增强的位置,使用通知定义了增强的逻辑,并将通知与切入点绑定来应用增强。

三、AOP简单实践

  1. 首先定义切面:在类上使用@Aspect表示其为一个切面,使用@Component将其Bean交给Spring管理。
  2. 然后定义切入点:切入点通过@Pointcut来定义,该注解可以作用于任意方法上(任意修饰符和返回值),格式如@Pointcut("execution(<访问权限> <返回类型> <包名.类名.方法名>(参数列表))"),支持使用表达式匹配。
  3. 最后定义通知:使用对应通知的注解加方法来定义。对应的切入点可以直接使用注解的pointcut属性定义,也可以引用提前定义好的切入点。
// 切面类
@Aspect
@Component
public class MyAspect {

    // 定义切入点
    @Pointcut("execution(* com.example.service.MyService.*(..))")
    public void myPointcut() {}

	// 前置通知 (引用切入点)
    @Before("myPointcut()")
    public void beforeAdvice(JoinPoint joinPoint) {
        System.out.println("Before: " + joinPoint.getSignature().getName() + " is called.");
    }

	// 后置通知 (直接定义切入点,可以省略pointcut属性)
	@After("execution(* com.example.service.MyService.*(..))")
	public void afterAdvice(JoinPoint joinPoint) {
		System.out.println("After: " + joinPoint.getSignature().getName() + " has executed.");
	}

	// 返回通知 (直接定义切入点,要处理返回值时不能省略pointcut属性,通过returning = "result"来将返回值注入给方法参数result)
	@AfterReturning(pointcut = "execution(* com.example.service.MyService.*(..))", returning = "result")
	public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
		System.out.println("AfterReturning: " + joinPoint.getSignature().getName() + " returned " + result);
	}

	// 异常通知 (直接定义切入点,要处理返回值时不能省略pointcut属性,通过throwing = "ex"来将异常注入给方法参数ex)
	@AfterThrowing(pointcut = "execution(* com.example.service.MyService.*(..))", throwing = "ex")
	public void afterThrowingAdvice(JoinPoint joinPoint, Exception ex) {
    	System.out.println("AfterThrowing: Method " + joinPoint.getSignature().getName() + " threw an exception: " + ex);
	}

	@Around("execution(* com.example.service.MyService.*(..))")
	public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
		System.out.println("Around: Before calling " + joinPoint.getSignature().getName());
		try {
        	// 执行目标方法
        	Object result = joinPoint.proceed();
        	System.out.println("Around: After calling " + joinPoint.getSignature().getName() + ", result was: " + result);
        	return result;
       	} catch (Exception ex) {
        	System.out.println("Around: Exception caught in " + joinPoint.getSignature().getName() + ": " + ex);
        	throw ex; // 可以选择处理异常或重新抛出
    	}
	}
}

四、AOP原理初探

Spring AOP使用JDK动态代理或CGLIB动态代理来创建目标对象的代理实例。如果目标对象实现了接口,Spring会使用JDK动态代理,通过java.lang.reflect.Proxy创建一个实现了相同接口的代理对象;若目标对象没有实现接口,Spring则会使用CGLIB库来创建目标对象的子类代理。

具体而言,若有一个类实现了某接口,切入点匹配的是接口方法,在调用被增强的方法时,会调用代理对象的增强后的方法,底层使用的是JDK动态代理来实代理对象,且代理对象也实现了该接口。需要注意的是,此时客户端代码必须持有对这些接口的引用,并通过接口来调用方法,这样才能触发生命周期内的切面逻辑。

若不是通过接口方法增强的,则会使用CGLIB动态代理,CGLIB代理允许你增强类中的任何非final、非static方法,包括私有方法(但Spring中不直接支持私有方法的AOP增强)。

参考链接

Spring AOP通知顺序

  • 15
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值