学习目标:
了解SpringAOP的基本概念、原理以及使用,帮助初学者入门。
学习内容:
Spring AOP 基本概念
Spring AOP 是 Spring 框架的一个重要组件,它允许你在不修改现有代码的情况下,通过"切面"(Aspect)来添加新的行为或责任。Spring AOP 主要用于实现诸如日志记录、事务管理、权限检查等功能,而这些功能通常被称为"横切关注点"(Cross-Cutting Concerns)。
Spring AOP 支持多种实现方式,主要包括:
动态代理
当目标对象实现了接口时,Spring 会使用 JDK 动态代理来创建代理对象。这种方式利用了 java.lang.reflect.Proxy 类来生成一个实现了目标接口的代理类。这个代理类可以拦截对目标对象的方法调用,并在调用前后添加额外的行为。
**优点:**如果目标对象实现了接口,那么使用 JDK 动态代理通常更轻量,因为不需要生成新的类。
**缺点:**只能用于实现了接口的对象
CGLIB 字节码增强
当目标对象没有实现接口时,Spring 会使用 CGLIB(Code Generation Library)来生成一个继承自目标类的新子类。这个子类同样可以拦截方法调用,并在调用前后添加行为。
**优点:**可以用于任何对象,不论其是否实现了接口。
**缺点:**生成新的子类,可能会导致类的数量增加,以及潜在的性能开销。
AOP核心概念
在 Spring AOP 中,AOP 的核心概念包括切入点(Pointcut)、连接点(Joinpoint)、通知(Advice)、切面(Aspect)等。下面将详细介绍这些概念:
连接点(Joinpoint)
定义: 连接点是指程序执行中的某个特定点,如方法调用、字段访问、异常抛出等。它是一个程序中的具体位置,在这个位置可以插入通知(Advice),连接点是程序中可以插入切面逻辑的位置,是切入点所匹配的具体位置。
作用: 连接点是切入点所匹配的具体位置。每一个切入点实际上匹配了一组连接点,即一组方法调用或事件。
切入点(Pointcut)
定义: 切入点是 AOP 中的一个术语,用于描述哪些地方(即哪些方法调用或类)会被 AOP 的通知所影响。简单来说,就是指明了那些地方将会应用切面的逻辑。切入点是一个或多个连接点的集合,它定义了哪些连接点将应用切面的逻辑。
作用: 切入点决定了通知何时何地执行。它可以是方法执行、异常抛出、构造函数调用等多种类型的连接点。
表达方式: 切入点可以通过表达式来定义,通常使用 Spring 的切入点表达式语言。例如:
(1) execution( com.example.service.. (...)) ** 这个切入点表达式表示匹配 com.example.service 包下的所有类的所有方法,可以通过多个||与execution组合。
(2) @annotation(org.springframework.stereotype.Service) * 这个切入点表达式表示匹配带有 @Service 注解的方法或类。也可以用自定义注解的方式来实匹配切入点方法,使用时只需要在对于类上加上该注解就可以。
注意: @PointCut注解是将公共的切入点表达式抽取出来,需要用到时引用该切入点表达式即可。
通知(Advice)
定义: 通知是 AOP 中执行的代码块,它在切入点所匹配的连接点处被调用。通知定义了切面在特定连接点上执行的行为。
通知类型:
前置通知(@Before): 在方法调用之前执行的通知。
后置通知(@After): 在方法调用之后执行的通知,无论方法是否成功执行。
返回后通知(@After Returning ): 在方法成功执行后执行的通知。
抛出异常后通知(@After Throwing ): 在方法抛出异常后执行的通知。
环绕通知(@Around 重点): 在方法调用前后都执行的通知,可以控制是否继续执行方法。
注意: @Around环绕通知需要自己调用ProceedingJoinPoint.proceed()来让原始方法执行,其他通知不需要考虑目标方法执行。
@Around环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回值。
通知执行顺序:
当有多个切面的切入点都匹配到了目标方法,目标方法运行时,多个通知方法都会被执行。那么就会有一个先后执行的顺序。
默认执行顺序: 按照切面类,类名的字母排名来确定的,排名越前的前置通知就先执行,相反它的后置通知就越靠后执行。
@Order注解: 加在切面类上来控制顺序,前置通知,数字越小越先执行,后置通知相反。
切面(Aspect)
定义: 切面是由一个或多个通知和相应的切入点组成的模块,它代表了一个横切关注点的实现。
作用: 切面是将横切关注点模块化的手段,它将与业务逻辑无关的代码(如日志记录、事务管理等)从业务逻辑中分离出来。
引入(Introduction)
定义: 引入允许我们在不修改类的情况下向其添加新的接口实现或方法。
作用: 通过引入,可以在不改变类的实现的情况下,让类拥有新的能力。例如,可以让一个类实现一个新的接口,或者添加新的方法。
下面通过一个简单的示例来说明这些概念的应用:
假设我们有一个日志记录的切面,它记录方法调用前后的信息。
切面类的定义
java
@Aspect
@Component
public class LoggingAspect {
/**通知,execution()切入点表达式*/
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("Before method call: " + joinPoint.getSignature().getName());
}
@AfterReturning(value = "execution(* com.example.service.*.*(..))", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
System.out.println("After method call: " + joinPoint.getSignature().getName() + ", Result: " + result);
}
@AfterThrowing(value = "execution(* com.example.service.*.*(..))", throwing = "exception")
public void logAfterThrowing(JoinPoint joinPoint, Exception exception) {
System.out.println("After method threw an exception: " + joinPoint.getSignature().getName() + ", Exception: " + exception.getMessage());
}
@After("execution(* com.example.service.*.*(..))")
public void logAfter(JoinPoint joinPoint) {
System.out.println("After method execution: " + joinPoint.getSignature().getName());
}
@Around("execution(* com.example.service.*.*(..))")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Before method call (around): " + joinPoint.getSignature().getName());
Object result = joinPoint.proceed();
System.out.println("After method call (around): " + joinPoint.getSignature().getName());
return result;
}
}
在这个示例中:
@Before 注解定义了一个前置通知,它在方法调用之前执行。
@AfterReturning 注解定义了一个返回后通知,它在方法成功执行后执行。
@AfterThrowing 注解定义了一个抛出异常后通知,它在方法抛出异常后执行。
@After 注解定义了一个后置通知,它在方法调用之后执行,无论方法是否成功执行。
@Around 注解定义了一个环绕通知,它在方法调用前后都执行,并且可以控制方法的执行流程。
`