面向切面编程 (AOP)
什么是AOP?
AOP (Aspect-Oriented Programming) 是一种编程范式,用于将横切关注点(cross-cutting concerns)从业务逻辑中分离出来,提高代码的模块化程度。
横切关注点示例
- 日志记录
- 事务管理
- 安全检查
- 性能监控
- 异常处理
- 缓存
核心概念
1. 切面 (Aspect)
横切关注点的模块化,包含切点和通知。
java
@Aspect
@Component
public class LoggingAspect {
// 这是一个切面
}2. 连接点 (Join Point)
程序执行过程中的某个点,如方法调用、异常抛出等。
java
// 方法执行就是一个连接点
public void saveUser(User user) {
// ...
}3. 切点 (Pointcut)
匹配连接点的表达式,定义在哪里应用通知。
java
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {
// 切点定义
}4. 通知 (Advice)
在特定连接点执行的动作。
- @Before:前置通知,方法执行前
- @After:后置通知,方法执行后(无论成功失败)
- @AfterReturning:返回通知,方法成功返回后
- @AfterThrowing:异常通知,方法抛出异常后
- @Around:环绕通知,方法执行前后
5. 织入 (Weaving)
将切面应用到目标对象的过程。
切点表达式
execution表达式
语法:
execution(modifiers? return-type declaring-type? method-name(params) throws?)示例:
java
// 匹配所有public方法
@Pointcut("execution(public * *(..))")
// 匹配service包下所有类的所有方法
@Pointcut("execution(* com.example.service.*.*(..))")
// 匹配service包及其子包下所有类的所有方法
@Pointcut("execution(* com.example.service..*.*(..))")
// 匹配UserService类的所有方法
@Pointcut("execution(* com.example.service.UserService.*(..))")
// 匹配所有save开头的方法
@Pointcut("execution(* save*(..))")
// 匹配第一个参数为String类型的方法
@Pointcut("execution(* *(String, ..))")
// 匹配返回User类型的方法
@Pointcut("execution(com.example.domain.User *(..))")within表达式
匹配指定类型内的方法执行。
java
// 匹配service包下所有类
@Pointcut("within(com.example.service.*)")
// 匹配service包及其子包下所有类
@Pointcut("within(com.example.service..*)")
// 匹配UserService类
@Pointcut("within(com.example.service.UserService)")@annotation表达式
匹配带有指定注解的方法。
java
// 匹配带有@Transactional注解的方法
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
// 自定义注解
@Pointcut("@annotation(com.example.annotation.Log)")组合表达式
java
// AND
@Pointcut("execution(* com.example.service.*.*(..)) && within(com.example.service.*)")
// OR
@Pointcut("execution(* save*(..)) || execution(* update*(..))")
// NOT
@Pointcut("execution(* com.example.service.*.*(..)) && !execution(* com.example.service.Internal*.*(..))")通知类型详解
1. @Before - 前置通知
在方法执行前执行。
java
@Aspect
@Component
public class SecurityAspect {
@Before("execution(* com.example.service.*.*(..))")
public void checkSecurity(JoinPoint joinPoint) {
System.out.println("安全检查: " + joinPoint.getSignature().getName());
// 执行安全检查逻辑
}
}2. @After - 后置通知
在方法执行后执行(无论成功或失败)。
java
@Aspect
@Component
public class LoggingAspect {
@After("execution(* com.example.service.*.*(..))")
public void logAfter(JoinPoint joinPoint) {
System.out.println("方法执行完成: " + joinPoint.getSignature().getName());
}
}3. @AfterReturning - 返回通知
在方法成功返回后执行。
java
@Aspect
@Component
public class LoggingAspect {
@AfterReturning(
pointcut = "execution(* com.example.service.*.*(..))",
returning = "result"
)
public void logAfterReturning(JoinPoint joinPoint, Object result) {
System.out.println("方法返回值: " + result);
}
}4. @AfterThrowing - 异常通知
在方法抛出异常后执行。
java
@Aspect
@Component
public class ExceptionHandlingAspect {
@AfterThrowing(
pointcut = "execution(* com.example.service.*.*(..))",
throwing = "ex"
)
public void logAfterThrowing(JoinPoint joinPoint, Exception ex) {
System.err.println("方法抛出异常: " + ex.getMessage());
// 记录异常日志
}
}5. @Around - 环绕通知
最强大的通知类型,可以完全控制方法执行。
java
@Aspect
@Component
public class PerformanceAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object measurePerformance(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
try {
// 执行目标方法
Object result = joinPoint.proceed();
return result;
} finally {
long duration = System.currentTimeMillis() - start;
System.out.println(joinPoint.getSignature().getName() +
" 执行时间: " + duration + "ms");
}
}
}实战示例
1. 日志切面
java
@Aspect
@Component
@Slf4j
public class LoggingAspect {
@Pointcut("execution(* com.example.service..*.*(..))")
public void serviceLayer() {}
@Before("serviceLayer()")
public void logBefore(JoinPoint joinPoint) {
log.info("执行方法: {}.{}",
joinPoint.getTarget().getClass().getName(),
joinPoint.getSignature().getName());
Object[] args = joinPoint.getArgs();
if (args.length > 0) {
log.info("方法参数: {}", Arrays.toString(args));
}
}
@AfterReturning(pointcut = "serviceLayer()", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
log.info("方法返回: {}", result);
}
@AfterThrowing(pointcut = "serviceLayer()", throwing = "ex")
public void logAfterThrowing(JoinPoint joinPoint, Exception ex) {
log.error("方法异常: {}.{}, 异常信息: {}",
joinPoint.getTarget().getClass().getName(),
joinPoint.getSignature().getName(),
ex.getMessage());
}
}2. 性能监控切面
java
@Aspect
@Component
public class PerformanceMonitorAspect {
private final Map<String, List<Long>> performanceData = new ConcurrentHashMap<>();
@Around("execution(* com.example.service..*.*(..))")
public Object monitorPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().toShortString();
long startTime = System.nanoTime();
try {
return joinPoint.proceed();
} finally {
long duration = System.nanoTime() - startTime;
recordPerformance(methodName, duration);
if (duration > 1_000_000_000) { // 超过1秒
System.err.println("警告: " + methodName +
" 执行时间过长: " + duration / 1_000_000 + "ms");
}
}
}
private void recordPerformance(String methodName, long duration) {
performanceData.computeIfAbsent(methodName, k -> new ArrayList<>())
.add(duration);
}
@Scheduled(fixedRate = 60000) // 每分钟报告一次
public void reportPerformance() {
performanceData.forEach((method, times) -> {
long avg = times.stream().mapToLong(Long::longValue).sum() / times.size();
System.out.println(method + " 平均执行时间: " + avg / 1_000_000 + "ms");
});
performanceData.clear();
}
}3. 缓存切面
java
@Aspect
@Component
public class CacheAspect {
private final Map<String, Object> cache = new ConcurrentHashMap<>();
@Around("@annotation(cacheable)")
public Object cache(ProceedingJoinPoint joinPoint, Cacheable cacheable) throws Throwable {
String key = generateKey(joinPoint);
// 尝试从缓存获取
Object cachedResult = cache.get(key);
if (cachedResult != null) {
System.out.println("缓存命中: " + key);
return cachedResult;
}
// 执行方法
Object result = joinPoint.proceed();
// 存入缓存
cache.put(key, result);
System.out.println("缓存更新: " + key);
return result;
}
private String generateKey(ProceedingJoinPoint joinPoint) {
String methodName = joinPoint.getSignature().toShortString();
String args = Arrays.toString(joinPoint.getArgs());
return methodName + ":" + args;
}
}
// 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Cacheable {
String value() default "";
}4. 事务切面(简化版)
java
@Aspect
@Component
public class TransactionAspect {
@Autowired
private PlatformTransactionManager transactionManager;
@Around("@annotation(org.springframework.transaction.annotation.Transactional)")
public Object manageTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
TransactionStatus status = transactionManager.getTransaction(
new DefaultTransactionDefinition()
);
try {
Object result = joinPoint.proceed();
transactionManager.commit(status);
System.out.println("事务提交成功");
return result;
} catch (Exception e) {
transactionManager.rollback(status);
System.err.println("事务回滚: " + e.getMessage());
throw e;
}
}
}5. 自定义注解切面
java
// 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequirePermission {
String value();
}
// 切面实现
@Aspect
@Component
public class PermissionAspect {
@Before("@annotation(requirePermission)")
public void checkPermission(JoinPoint joinPoint, RequirePermission requirePermission) {
String requiredPermission = requirePermission.value();
// 获取当前用户
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
// 检查权限
boolean hasPermission = auth.getAuthorities().stream()
.anyMatch(a -> a.getAuthority().equals(requiredPermission));
if (!hasPermission) {
throw new AccessDeniedException("没有权限: " + requiredPermission);
}
}
}
// 使用
@Service
public class UserService {
@RequirePermission("USER:DELETE")
public void deleteUser(Long userId) {
// 删除用户
}
}配置AOP
启用AOP
java
@Configuration
@EnableAspectJAutoProxy
public class AopConfig {
// 配置类
}依赖配置
Maven:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>最佳实践
合理使用切点表达式
- 尽量精确匹配,避免过度拦截
- 使用组合表达式提高可读性
选择合适的通知类型
- 简单场景用@Before或@After
- 需要控制流程用@Around
- 处理异常用@AfterThrowing
注意性能影响
- AOP会增加方法调用开销
- 避免在高频方法上使用复杂切面
避免循环依赖
- 切面不要依赖被切的Bean
- 使用@Lazy延迟加载
切面执行顺序
- 使用@Order注解控制顺序
- 数字越小优先级越高
java
@Aspect
@Component
@Order(1)
public class FirstAspect {
// 优先执行
}
@Aspect
@Component
@Order(2)
public class SecondAspect {
// 后执行
}- 测试切面
java
@SpringBootTest
class LoggingAspectTest {
@Autowired
private UserService userService;
@Test
void testLoggingAspect() {
// 测试切面是否正常工作
userService.getUser(1L);
}
}常见问题
1. 切面不生效
可能原因:
- 没有启用AOP:添加@EnableAspectJAutoProxy
- 切点表达式不正确
- 目标类没有被Spring管理
- 类内部调用方法(self-invocation)
2. 类内部调用问题
java
@Service
public class UserService {
public void methodA() {
// 直接调用,AOP不会生效
this.methodB();
}
@Transactional
public void methodB() {
// ...
}
}解决方案:
java
@Service
public class UserService {
@Autowired
private ApplicationContext context;
public void methodA() {
// 通过代理对象调用
UserService proxy = context.getBean(UserService.class);
proxy.methodB();
}
@Transactional
public void methodB() {
// ...
}
}总结
AOP是Spring框架的核心特性之一,它让我们能够:
- 分离横切关注点
- 提高代码复用性
- 降低代码耦合度
- 简化开发和维护
合理使用AOP可以大大提升代码质量和开发效率。