最近在重构一个Spring Boot项目时,需要对所有接口添加统一的日志记录和性能监控,使用传统方式需要修改每个方法,工作量巨大且代码冗余。Spring AOP(面向切面编程)完美解决了这个问题,本文将深入剖析Spring AOP的使用方式和底层原理。
AOP基本概念
AOP(Aspect-Oriented Programming,面向切面编程)是对OOP(面向对象编程)的补充,它将系统中的横切关注点(如日志、事务、权限等)从业务逻辑中分离出来,实现关注点分离。
核心术语
在深入Spring AOP之前,先理解几个核心概念:
- Aspect(切面):横切关注点的模块化,比如日志模块、事务管理模块
- Join Point(连接点):程序执行过程中的某个点,Spring AOP中特指方法执行
- Advice(通知):切面在特定连接点执行的动作,分为前置、后置、环绕、异常、最终通知
- Pointcut(切入点):匹配连接点的表达式,决定哪些方法需要被增强
- Target(目标对象):被AOP代理的原始对象
- Proxy(代理):AOP框架创建的对象,包含了原始对象的功能和增强功能
- Weaving(织入):将切面应用到目标对象创建代理对象的过程
Spring AOP快速上手
引入依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
基础使用示例
创建一个简单的日志切面:
java
@Aspect
@Component
@Slf4j
public class LoggingAspect {
// 定义切入点:匹配controller包下所有类的所有方法
@Pointcut("execution(* com.example.controller.*.*(..))")
public void controllerMethods() {}
// 前置通知:方法执行前记录
@Before("controllerMethods()")
public void logBefore(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
log.info("方法 {} 开始执行,参数:{}", methodName, Arrays.toString(args));
}
// 后置返回通知:方法正常返回后
@AfterReturning(pointcut = "controllerMethods()", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
log.info("方法 {} 执行完成,返回值:{}", methodName, result);
}
// 异常通知:方法抛出异常时
@AfterThrowing(pointcut = "controllerMethods()", throwing = "ex")
public void logAfterThrowing(JoinPoint joinPoint, Exception ex) {
String methodName = joinPoint.getSignature().getName();
log.error("方法 {} 执行异常:{}", methodName, ex.getMessage());
}
}
环绕通知实现性能监控
环绕通知是最强大的通知类型,可以完全控制方法的执行:
java
@Aspect
@Component
public class PerformanceAspect {
@Around("@annotation(com.example.annotation.MonitorPerformance)")
public Object monitorPerformance(ProceedingJoinPoint pjp) throws Throwable {
long startTime = System.currentTimeMillis();
// 获取方法信息
String className = pjp.getTarget().getClass().getSimpleName();
String methodName = pjp.getSignature().getName();
try {
// 执行目标方法
Object result = pjp.proceed();
long elapsedTime = System.currentTimeMillis() - startTime;
if (elapsedTime > 1000) {
log.warn("{}.{} 执行时间过长:{}ms", className, methodName, elapsedTime);
} else {
log.info("{}.{} 执行时间:{}ms", className, methodName, elapsedTime);
}
return result;
} catch (Exception e) {
long elapsedTime = System.currentTimeMillis() - startTime;
log.error("{}.{} 执行异常,耗时:{}ms", className, methodName, elapsedTime, e);
throw e;
}
}
}
自定义注解:
java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MonitorPerformance {
String value() default "";
}
使用示例:
java
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
@MonitorPerformance
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
}
切入点表达式详解
Spring AOP支持多种切入点表达式,最常用的是execution表达式:
execution表达式语法
text
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
示例:
java
// 匹配所有public方法
@Pointcut("execution(public * *(..))")
// 匹配指定包下所有类的所有方法
@Pointcut("execution(* com.example.service.*.*(..))")
// 匹配指定类的所有方法
@Pointcut("execution(* com.example.service.UserService.*(..))")
// 匹配所有返回User类型的方法
@Pointcut("execution(com.example.model.User *(..))")
// 匹配所有以save开头的方法
@Pointcut("execution(* save*(..))")
// 匹配第一个参数为Long类型的方法
@Pointcut("execution(* *(Long, ..))")
其他切入点表达式
java
@Aspect
@Component
public class AdvancedPointcutAspect {
// within:匹配特定类型内的方法
@Pointcut("within(com.example.service.*)")
public void inServiceLayer() {}
// @within:匹配标注了特定注解的类
@Pointcut("@within(org.springframework.stereotype.Service)")
public void serviceAnnotated() {}
// @annotation:匹配标注了特定注解的方法
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
public void transactionalMethods() {}
// args:匹配特定参数类型
@Pointcut("args(String, Long)")
public void stringLongArgs() {}
// @args:匹配参数标注了特定注解
@Pointcut("@args(com.example.annotation.Validated)")
public void validatedArgs() {}
// bean:匹配特定名称的Bean
@Pointcut("bean(*Service)")
public void serviceBeans() {}
// 组合切入点
@Pointcut("inServiceLayer() && transactionalMethods()")
public void transactionalServiceMethods() {}
}
实战案例
统一异常处理与日志记录
java
@Aspect
@Component
@Slf4j
public class ExceptionHandlingAspect {
@Around("@within(org.springframework.web.bind.annotation.RestController)")
public Object handleException(ProceedingJoinPoint pjp) throws Throwable {
String methodName = pjp.getSignature().toShortString();
try {
return pjp.proceed();
} catch (BusinessException e) {
// 业务异常,记录警告日志
log.warn("业务异常 in {}: {}", methodName, e.getMessage());
return ResponseEntity.badRequest().body(
Map.of("error", e.getMessage(), "code", e.getCode())
);
} catch (Exception e) {
// 系统异常,记录错误日志
log.error("系统异常 in {}", methodName, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(
Map.of("error", "系统内部错误", "code", "SYSTEM_ERROR")
);
}
}
}
方法级缓存实现
java
@Aspect
@Component
public class CachingAspect {
private final Map<String, Object> cache = new ConcurrentHashMap<>();
@Around("@annotation(cacheable)")
public Object cache(ProceedingJoinPoint pjp, Cacheable cacheable) throws Throwable {
// 生成缓存key
String key = generateKey(pjp, cacheable.key());
// 检查缓存
if (cache.containsKey(key)) {
log.debug("缓存命中: {}", key);
return cache.get(key);
}
// 执行方法
Object result = pjp.proceed();
// 存入缓存
if (result != null) {
cache.put(key, result);
log.debug("缓存存储: {}", key);
// 设置过期(简化示例,实际应使用更完善的缓存方案)
if (cacheable.expire() > 0) {
scheduleEviction(key, cacheable.expire());
}
}
return result;
}
private String generateKey(ProceedingJoinPoint pjp, String keyExpression) {
if (StringUtils.hasText(keyExpression)) {
// 使用SpEL解析key表达式
return parseSpel(keyExpression, pjp);
}
// 默认使用类名+方法名+参数
return pjp.getTarget().getClass().getName() + "." +
pjp.getSignature().getName() +
Arrays.toString(pjp.getArgs());
}
private void scheduleEviction(String key, long seconds) {
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
executor.schedule(() -> cache.remove(key), seconds, TimeUnit.SECONDS);
}
}
分布式锁实现
java
@Aspect
@Component
public class DistributedLockAspect {
@Autowired
private RedissonClient redissonClient;
@Around("@annotation(distributedLock)")
public Object lock(ProceedingJoinPoint pjp, DistributedLock distributedLock) throws Throwable {
String lockKey = resolveLockKey(pjp, distributedLock);
RLock lock = redissonClient.getLock(lockKey);
boolean acquired = false;
try {
// 尝试获取锁
acquired = lock.tryLock(
distributedLock.waitTime(),
distributedLock.leaseTime(),
TimeUnit.SECONDS
);
if (!acquired) {
throw new BusinessException("获取锁失败,请稍后重试");
}
log.info("获取分布式锁成功: {}", lockKey);
return pjp.proceed();
} finally {
if (acquired && lock.isHeldByCurrentThread()) {
lock.unlock();
log.info("释放分布式锁: {}", lockKey);
}
}
}
private String resolveLockKey(ProceedingJoinPoint pjp, DistributedLock annotation) {
String key = annotation.key();
if (key.contains("#")) {
// 解析SpEL表达式
return parseSpelKey(key, pjp);
}
return "lock:" + key;
}
}
Spring AOP原理
代理模式基础
Spring AOP基于代理模式实现,主要有两种代理方式:
JDK动态代理
适用于实现了接口的类:
java
public class JdkProxyExample {
interface UserService {
void save(String user);
}
static class UserServiceImpl implements UserService {
@Override
public void save(String user) {
System.out.println("保存用户: " + user);
}
}
static class LoggingHandler implements InvocationHandler {
private final Object target;
public LoggingHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法执行前: " + method.getName());
Object result = method.invoke(target, args);
System.out.println("方法执行后: " + method.getName());
return result;
}
}
public static void main(String[] args) {
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new LoggingHandler(target)
);
proxy.save("张三");
}
}
CGLIB代理
适用于没有实现接口的类:
java
public class CglibProxyExample {
static class UserService {
public void save(String user) {
System.out.println("保存用户: " + user);
}
}
static class LoggingInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("方法执行前: " + method.getName());
Object result = proxy.invokeSuper(obj, args);
System.out.println("方法执行后: " + method.getName());
return result;
}
}
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setCallback(new LoggingInterceptor());
UserService proxy = (UserService) enhancer.create();
proxy.save("李四");
}
}
Spring AOP代理创建流程
Spring通过DefaultAopProxyFactory
决定使用哪种代理方式:
java
public class DefaultAopProxyFactory implements AopProxyFactory {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
// 如果满足以下条件之一,使用CGLIB代理:
// 1. 配置强制使用CGLIB(proxy-target-class=true)
// 2. 目标类没有实现接口
// 3. 只代理了SpringProxy接口
if (config.isOptimize() || config.isProxyTargetClass() ||
hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class");
}
// 如果目标类是接口或者是代理类,使用JDK代理
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
// 使用CGLIB代理
return new ObjenesisCglibAopProxy(config);
} else {
// 使用JDK代理
return new JdkDynamicAopProxy(config);
}
}
}
@AspectJ注解处理流程
Spring容器启动时,AnnotationAwareAspectJAutoProxyCreator
负责处理@AspectJ注解:
java
public class AnnotationAwareAspectJAutoProxyCreator extends AspectJAwareAdvisorAutoProxyCreator {
@Override
protected List<Advisor> findCandidateAdvisors() {
// 调用父类方法查找所有Advisor
List<Advisor> advisors = super.findCandidateAdvisors();
// 查找@AspectJ注解的切面
if (this.aspectJAdvisorsBuilder != null) {
advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
}
return advisors;
}
// 构建切面通知
public List<Advisor> buildAspectJAdvisors() {
List<String> aspectNames = this.aspectBeanNames;
if (aspectNames == null) {
synchronized (this) {
aspectNames = this.aspectBeanNames;
if (aspectNames == null) {
List<Advisor> advisors = new ArrayList<>();
aspectNames = new ArrayList<>();
// 获取所有Bean名称
String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this.beanFactory, Object.class, true, false);
for (String beanName : beanNames) {
Class<?> beanType = this.beanFactory.getType(beanName);
// 判断是否为@Aspect注解的类
if (this.advisorFactory.isAspect(beanType)) {
aspectNames.add(beanName);
// 获取切面元数据
AspectMetadata amd = new AspectMetadata(beanType, beanName);
// 创建Advisor
List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(
new BeanFactoryAspectInstanceFactory(
this.beanFactory, beanName));
advisors.addAll(classAdvisors);
}
}
this.aspectBeanNames = aspectNames;
return advisors;
}
}
}
return Collections.emptyList();
}
}
通知执行链
当代理对象的方法被调用时,会构建一个拦截器链:
java
public class ReflectiveMethodInvocation implements ProxyMethodInvocation {
protected final Object proxy;
protected final Object target;
protected final Method method;
protected Object[] arguments;
protected final List<?> interceptorsAndDynamicMethodMatchers;
private int currentInterceptorIndex = -1;
@Override
public Object proceed() throws Throwable {
// 所有拦截器执行完毕,执行目标方法
if (this.currentInterceptorIndex ==
this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
// 获取下一个拦截器
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
// 动态匹配
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
} else {
// 跳过不匹配的拦截器
return proceed();
}
} else {
// 执行拦截器
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
}
性能优化
合理使用切入点表达式
java
// 不推荐:范围太广
@Pointcut("execution(* *(..))")
// 推荐:精确匹配
@Pointcut("execution(* com.example.service.*Service.*(..))")
避免在通知中执行耗时操作
java
@Aspect
@Component
public class AsyncLoggingAspect {
@Autowired
private AsyncTaskExecutor taskExecutor;
@AfterReturning("@annotation(Loggable)")
public void logAsync(JoinPoint joinPoint) {
// 异步执行耗时操作
taskExecutor.execute(() -> {
// 耗时的日志处理
processLog(joinPoint);
});
}
}
使用编译时织入提升性能
对于性能敏感的场景,可以使用AspectJ编译时织入(CTW)或加载时织入(LTW):
xml
<!-- 启用AspectJ编译时织入 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.14.0</version>
<configuration>
<complianceLevel>11</complianceLevel>
<source>11</source>
<target>11</target>
<showWeaveInfo>true</showWeaveInfo>
<aspectLibraries>
<aspectLibrary>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</aspectLibrary>
</aspectLibraries>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
常见问题与解决方案
自调用失效问题
java
@Service
public class UserService {
@Transactional
public void saveUser(User user) {
// 直接调用本类方法,AOP不生效
sendNotification(user); // 错误!
// 正确方式1:注入自身代理
((UserService) AopContext.currentProxy()).sendNotification(user);
// 正确方式2:将方法移到其他类
}
@Async
public void sendNotification(User user) {
// 发送通知
}
}
// 启用暴露代理
@EnableAspectJAutoProxy(exposeProxy = true)
final方法和类的问题
CGLIB无法代理final方法和final类:
java
// 错误:final类无法被CGLIB代理
@Service
public final class UserService {
// ...
}
// 错误:final方法无法被增强
@Service
public class UserService {
public final void save() {
// ...
}
}
切面执行顺序
使用@Order注解控制多个切面的执行顺序:
java
@Aspect
@Component
@Order(1) // 数字越小,优先级越高
public class SecurityAspect {
// 安全检查
}
@Aspect
@Component
@Order(2)
public class LoggingAspect {
// 日志记录
}
@Aspect
@Component
@Order(3)
public class CachingAspect {
// 缓存处理
}
写在最后
AOP适合处理那些与业务逻辑正交的横切关注点,对于核心业务逻辑,还是应该使用传统的OOP方式来组织代码。