Spring面向切面编程之@Aspect和@Pointcut注解
Spring面向切面编程之@Aspect和@Pointcut注解
Spring面向切面编程之@Aspect和@Pointcut注解
1、前言
在Spring框架中,面向切面编程(Aspect-Oriented Programming,AOP)是一种编程范式,用于增强应用程序的模块化性和可重用性。AOP通过在程序执行过程中动态地将额外的行为织入到代码中,可以解耦和横切系统的关注点。
在传统的面向对象编程中,我们将应用程序的功能和业务逻辑以对象的形式进行封装和组织。然而,在某些情况下,我们需要处理跨多个对象、模块或层的关注点,如日志记录、事务管理、安全性等。这些关注点被称为横切关注点(cross-cutting concerns),它们与应用程序的核心逻辑相交织在一起,导致代码的重复和耦合。
AOP通过引入切面(Aspect)来解决这个问题。切面是横切关注点的模块化封装,它包含了横切关注点的逻辑和代码。在Spring中,切面可以通过使用基于注解或XML配置的方式定义。切面会被织入到应用程序的目标对象中,在目标对象的特定连接点(Join Point)上执行相应的行为。
2、AOP核心概念
面向切面编程则是指,对于一个我们已经封装好的类,我们可以在编译期间或在运行期间,对其进行切割,在原有的方法里面添加(织入)一些新的代码,对原有的方法代码进行一次增强处理。而那些增强部分的代码,就被称之为切面,切入点即切点,在切入点执行的动作即通知。
- 切面(Aspect)
切面是通知和切点的组合。它是代码模块化的单元,封装了特定关注点的逻辑和行为。切面定义了在哪些切点上应该执行特定的通知。 - 切点(Pointcut)
一个匹配连接点的表达式,指定了在应用程序中哪些连接点应该被拦截和处理。即要对哪些类中的哪些方法进行增强,进行切割,指的是被增强的方法。 - 连接点(Join Point)
程序执行过程中的一个点,如方法调用或异常抛出。连接点可以是方法的执行、方法的调用、对象的创建与初始化等。知道了要切哪些方法后,剩下的就是什么时候切,在原方法的哪一个执行阶段加入增加代码,这个就是连接点。 - 通知(Advice)
通知指定了在切点上要执行的逻辑。通知可以是在切点之前、之后或周围执行。在Spring中,常见的通知类型有前置通知、后置通知、返回通知和异常通知。 - 织入(Weaving)
织入是将切面应用到目标对象中的过程。它可以在编译时、类加载时或运行时发生。Spring使用代理织入和字节码织入两种主要的织入方式。
通知的类型
| 注解 | 类型 | 说明 |
|---|---|---|
| @Before | 前置通知 | 在目标方法执行前执行的通知。 |
| @After | 后置通知 | 在目标方法执行后执行的通知。无论是否有异常都会执行 |
| @Around | 环绕通知 | 在目标方法执行前后执行的自定义行为。 |
| @AfterReturning | 返回后通知 | 在目标方法执行后执行的通知。有异常不会执行 |
| @AfterThrowing | 异常后通知 | 在目标方法抛出异常后执行的通知。 |
3、AOP作用领域
| 领域 | 说明 |
|---|---|
| 日志记录 | 通过AOP可以实现自动的日志记录,记录方法的调用、参数、返回值等,方便系统的监控和问题排查。 |
| 事务管理 | AOP可以实现声明式事务管理,将事务处理的代码从业务逻辑中分离出来,使得代码更加简洁和易于维护。 |
| 安全性 | 通过AOP可以实现对系统的安全性进行管理,例如进行身份验证、权限检查等。 |
| 缓存管理 | AOP可以实现对方法的结果进行缓存,在方法执行前先检查缓存,提高系统的性能和响应速度。 |
| 异常处理 | 通过AOP可以实现统一的异常处理,将异常捕获并进行处理,避免异常的冒泡传播。 |
| 性能监控 | AOP可以用于对方法执行时间、资源消耗等进行监控和统计,以便进行性能优化和调整。 |
4、AOP核心注解
(1)@Aspect
@Aspect 用于标记一个类为切面类。切面类可以定义多个通知方法,并通过切点表达式指定这些方法在哪些连接点(Join Point)生效。
用法:
- 在类上添加
@Aspect注解; - 需要配合
@Component或@Service注解,确保Spring容器管理该类; - 启用AOP支持(如
@EnableAspectJAutoProxy)。
示例:
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
// 定义@Pointcut 和 各种Advice
}
(2)@Pointcut
@Pointcut 用于定义切点表达式,指定哪些方法或类需要被AOP增强。切点表达式可以是方法匹配规则(如包路径、方法名、参数等),它允许你写一次表达式,然后在多个通知中引用它。
用法:
- 在切面类中定义一个方法,并用
@Pointcut注解标记; - 切点表达式通常使用 AspectJ 的语法(如 execution、within 等);
- 其他通知方法(如
@Before、@After)可以直接引用该切点。
常用切点表达式
- execution:匹配方法执行(最常用的表达式)。
格式:execution(修饰符 返回值 包路径.类名.方法名(参数))
示例:
@Pointcut("execution(public * com.example.service.*.*(..))")
// 释义:
// execution( : 执行表达式开始
// public : 方法访问修饰符(可省略,表示所有)
// * : 返回值类型(* 代表任意类型)
// com.example.service: 包名
// .* : 包下的任何类
// .* : 类中的任何方法
// (..) : 方法的参数(.. 代表任意数量、任意类型的参数)
// )
public void serviceLayer() {} // 方法名即为切点的名称(标识符)
- within:匹配指定包或类下的所有方法。
示例:
@Pointcut("within(com.example.service.*)") // service包下任何类的方法
// @Pointcut("within(com.example.service.UserService)") // UserService类的所有方法
public void withinExample() {}
- this / target:基于代理对象或目标对象的类型进行匹配。
示例:
@Pointcut("target(com.example.service.MyService)") // 目标对象实现了MyService接口
public void interfaceExample() {}
@Pointcut("this(com.example.proxy.MyProxy)") // 代理对象是MyProxy类型
public void interfaceExample() {}
- args:匹配参数类型符合指定类型的方法。
示例:
@Pointcut("execution(* *..*(Long, ..))") // 第一个参数是Long类型的方法
public void argsExample() {}
- @annotation:匹配带有指定注解的方法。非常灵活和强大!
示例:
@Pointcut("@annotation(com.example.annotation.MyLoggable)") // 被@MyLoggable注解标记的方法
public void annotationExample() {}
用法示例:
定义一个空方法,方法体通常为空,并在其上使用 @Pointcut 注解。
@Aspect
@Component
public class LoggingAspect {
// 定义一个切点:匹配com.example.service包下所有类的所有公共方法
@Pointcut("execution(public * com.example.service.*.*(..))")
public void serviceLayerPointcut() {} // 方法签名就是切点的ID
// 定义另一个切点:匹配带有@Transactional注解的方法
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
public void transactionalMethod() {}
// 组合切点:匹配service层 或 带有@Transactional注解的方法
@Pointcut("serviceLayerPointcut() || transactionalMethod()")
public void combinedPointcut() {}
// 接下来,可以在通知中引用这些切点
}
5、最佳实践
- 自定义一个注解
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
// 只是一个标记注解
}
- 定义切面类
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
@Aspect
@Component
public class PerformanceAspect {
// 定义切点:所有被@LogExecutionTime注解的方法
@Pointcut("@annotation(LogExecutionTime)")
public void logTimePointcut() {}
// 使用 Around 通知来计算方法执行时间
@Around("logTimePointcut()") // 引用上面定义的切点
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
// 使用Spring的StopWatch计时
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 执行目标方法
Object result = joinPoint.proceed();
stopWatch.stop();
long executionTime = stopWatch.getTotalTimeMillis();
// 记录日志
System.out.println(joinPoint.getSignature() + " executed in " + executionTime + "ms");
return result;
}
}
- 在业务方法上使用自定义注解
@Service
public class UserService {
@LogExecutionTime // 应用切面
public User getUserById(Long id) {
// 模拟一些耗时操作
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// ...
}
return new User(id, "John Doe");
}
}
- 测试
调用getUserById()方法会输出:User com.example.service.UserService.getUserById(Long) executed in 501ms
6、补充:AOP与OOP的区别及各自的优势
(1)区别
- 关注点不同:OOP关注对象及其行为,将代码组织成对象的集合,通过封装、继承和多态等机制实现代码的模块化。而AOP关注横切关注点,这些关注点通常不是特定的类或对象所特有的,而是跨越多个对象或层次的通用功能。
- 抽象级别不同:OOP在语言级别上提供了类、继承、多态等概念和机制,以支持面向对象的设计和编程。而AOP是对OOP的一种补充,它在更高的抽象级别上考虑系统中的横切关注点,通过横切关注点的抽象描述和通用切面逻辑来增强系统的功能。
- 关注点分离:OOP通过封装将相关逻辑组织在对象中,实现了关注点的局部化。而AOP通过将横切关注点从业务逻辑中分离出来,实现了关注点的集中化和复用。
(2)优势
OOP的优势
- 模块化和可维护性:OOP通过封装、继承和多态等机制,实现了代码的模块化,使得代码易于理解、扩展和维护。
- 重用性:OOP提倡代码的重用,可以通过继承和组合等方式实现代码的复用,减少代码冗余。
- 可扩展性:OOP的继承和多态机制使得系统易于扩展和演化,可以通过添加新的子类或实现新的接口来引入新功能。
AOP的优势
- 关注点分离:AOP将横切关注点从业务逻辑中分离出来,减少了代码的重复性,增加了代码的可维护性和可重用性。
- 横向扩展:AOP可以轻松地在不修改目标对象代码的情况下,添加、修改或删除横切逻辑,改变系统的行为,提高了系统的灵活性。
- 增强系统功能:AOP可以通过在运行时动态地织入切面,增加新的功能和行为,如日志记录、安全检查、事务管理等。
更多推荐

所有评论(0)