Spring AOP 切面编程 实践指南
一、AOP的核心概念解析
1.1 横切关注点(Cross-cutting Concerns)
在软件开发中,某些功能会跨越系统的多个模块,例如:
- 日志记录
- 事务管理
- 安全控制
- 性能监控
- 异常处理
传统OOP编程模式下,这些功能代码会分散在各个业务模块中,导致:
// 用户服务示例
public class UserService {
public void createUser(User user) {
// 记录日志
log.info("开始创建用户");
try {
// 开启事务
transactionManager.begin();
// 核心业务逻辑
userDao.save(user);
// 提交事务
transactionManager.commit();
} catch (Exception e) {
// 异常处理
transactionManager.rollback();
log.error("创建用户失败", e);
}
// 记录日志
log.info("用户创建完成");
}
}
1.2 AOP核心术语图解
(此处应有ASCII示意图,因格式限制改为文字描述)
[客户端] --> [代理对象] --> [目标对象]
↗ 前置通知
↖ 后置通知
← 环绕通知
↘ 异常通知
↙ 最终通知
1.3 Spring AOP与AspectJ对比
特性 | Spring AOP | AspectJ |
---|---|---|
实现方式 | 动态代理 | 字节码增强 |
织入时机 | 运行时 | 编译时/加载时 |
功能范围 | 方法级别 | 字段/构造器/方法 |
性能 | 较好 | 优秀 |
依赖 | Spring容器 | 独立编译器 |
学习曲线 | 较低 | 较高 |
二、Spring AOP底层实现原理
2.1 代理模式深度解析
JDK动态代理示例代码:
public class JdkProxyDemo {
interface Service {
void execute();
}
static class RealService implements Service {
public void execute() {
System.out.println("实际业务执行");
}
}
static class LogInvocationHandler implements InvocationHandler {
private final Object target;
public LogInvocationHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法执行前记录日志");
Object result = method.invoke(target, args);
System.out.println("方法执行后记录日志");
return result;
}
}
public static void main(String[] args) {
Service realService = new RealService();
Service proxyService = (Service) Proxy.newProxyInstance(
Service.class.getClassLoader(),
new Class[]{Service.class},
new LogInvocationHandler(realService));
proxyService.execute();
}
}
CGLIB代理流程图:
1. 生成目标类的子类
2. 覆盖父类方法
3. 在方法调用前后加入增强逻辑
4. 调用方法时实际上调用的是子类方法
2.2 织入(Weaving)过程分析
Spring AOP支持三种织入方式:
- 编译时织入:使用AspectJ编译器
- 类加载时织入:使用特殊类加载器
- 运行时织入:Spring默认方式,通过动态代理
性能对比:
- 编译时织入:启动快,运行快
- 运行时织入:启动快,运行稍慢
- 类加载时织入:启动稍慢,运行快
三、Spring AOP详细配置
3.1 XML配置方式详解
<!-- 定义切面 -->
<aop:config>
<aop:aspect id="logAspect" ref="logAspectBean">
<!-- 定义切入点 -->
<aop:pointcut id="serviceMethods"
expression="execution(* com.example.service.*.*(..))"/>
<!-- 前置通知 -->
<aop:before pointcut-ref="serviceMethods" method="beforeAdvice"/>
<!-- 后置通知 -->
<aop:after-returning pointcut-ref="serviceMethods"
method="afterReturningAdvice" returning="result"/>
<!-- 异常通知 -->
<aop:after-throwing pointcut-ref="serviceMethods"
method="afterThrowingAdvice" throwing="ex"/>
<!-- 最终通知 -->
<aop:after pointcut-ref="serviceMethods" method="afterFinallyAdvice"/>
<!-- 环绕通知 -->
<aop:around pointcut-ref="serviceMethods" method="aroundAdvice"/>
</aop:aspect>
</aop:config>
3.2 注解配置最佳实践
@Aspect
@Component
public class LoggingAspect {
// 定义切入点表达式
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}
@Before("serviceMethods()")
public void logMethodStart(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("方法开始执行: " + methodName);
}
@Around("serviceMethods()")
public Object measureExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
long duration = System.currentTimeMillis() - start;
System.out.println("方法执行耗时: " + duration + "ms");
return result;
}
@AfterThrowing(pointcut = "serviceMethods()", throwing = "ex")
public void logException(JoinPoint joinPoint, Exception ex) {
System.err.println("方法执行异常: " + ex.getMessage());
}
}
四、Spring Boot中AOP的进阶用法
4.1 自定义注解实现切面
创建自定义注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuditLog {
String value() default "";
OperationType type() default OperationType.QUERY;
}
public enum OperationType {
CREATE, UPDATE, DELETE, QUERY
}
实现切面逻辑:
@Aspect
@Component
public class AuditLogAspect {
@Autowired
private AuditLogRepository auditLogRepository;
@Around("@annotation(auditLog)")
public Object auditLogging(ProceedingJoinPoint joinPoint, AuditLog auditLog) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
AuditLogEntity logEntity = new AuditLogEntity();
logEntity.setOperationType(auditLog.type());
logEntity.setDescription(StringUtils.isBlank(auditLog.value()) ?
method.getName() : auditLog.value());
logEntity.setStartTime(LocalDateTime.now());
try {
Object result = joinPoint.proceed();
logEntity.setStatus(LogStatus.SUCCESS);
return result;
} catch (Exception e) {
logEntity.setStatus(LogStatus.FAILED);
logEntity.setErrorDetail(e.getMessage());
throw e;
} finally {
logEntity.setEndTime(LocalDateTime.now());
auditLogRepository.save(logEntity);
}
}
}
4.2 多切面执行顺序控制
- 实现
Ordered
接口:
@Aspect
@Component
public class SecurityAspect implements Ordered {
// 切面逻辑...
@Override
public int getOrder() {
return 1; // 最高优先级
}
}
@Aspect
@Component
public class LoggingAspect implements Ordered {
// 切面逻辑...
@Override
public int getOrder() {
return 2;
}
}
- 使用
@Order
注解:
@Aspect
@Order(1)
@Component
public class SecurityAspect { /* ... */ }
@Aspect
@Order(2)
@Component
public class LoggingAspect { /* ... */ }
执行顺序:
- SecurityAspect @Around前处理
- LoggingAspect @Around前处理
- 目标方法执行
- LoggingAspect @Around后处理
- SecurityAspect @Around后处理
五、性能优化与最佳实践
5.1 切入点表达式优化技巧
优化前:
@Pointcut("execution(* com.example..*.*(..))")
public void allMethods() {}
优化方案:
- 缩小匹配范围:
@Pointcut("execution(public * com.example.service.*Service.*(..))")
- 组合使用切入点指示符:
@Pointcut("within(@org.springframework.stereotype.Service *) && " +
"execution(* *(..))")
- 使用注解过滤:
@Pointcut("@annotation(com.example.annotation.Monitored)")
5.2 代理模式选择策略
配置方式(application.properties):
# 强制使用CGLIB代理
spring.aop.proxy-target-class=true
选择建议:
- 目标类实现了接口 → 默认使用JDK代理
- 目标类未实现接口 → 必须使用CGLIB
- 需要代理非接口方法 → 使用CGLIB
- 性能要求极高 → 考虑AspectJ编译时织入
六、常见问题解决方案
6.1 切面不生效排查清单
- 检查切面类是否被Spring管理(是否有@Component等注解)
- 确认启动类添加了@EnableAspectJAutoProxy
- 验证切入点表达式是否正确匹配目标方法
- 检查目标方法是否为public(Spring AOP默认只代理public方法)
- 确认没有多个同类型Bean导致代理冲突
- 查看是否被final修饰(CGLIB无法代理final类/方法)
6.2 循环依赖问题处理
典型错误场景:
AspectA -> 依赖 -> ServiceB
ServiceB -> 依赖 -> ServiceA
ServiceA -> 被AspectA代理
解决方案:
- 使用setter注入代替构造器注入
- 调整Bean初始化顺序
- 使用@Lazy延迟加载
- 重构代码消除循环依赖
@Component
public class ServiceA {
private final ServiceB serviceB;
@Lazy
public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
七、AOP在分布式系统中的应用
7.1 分布式链路追踪实现
@Aspect
@Component
public class DistributedTracingAspect {
@Around("execution(* com.example.microservice..*(..))")
public Object handleTracing(ProceedingJoinPoint joinPoint) throws Throwable {
String traceId = MDC.get("traceId");
if (traceId == null) {
traceId = UUID.randomUUID().toString();
MDC.put("traceId", traceId);
}
try {
log.info("开始分布式调用: {}", joinPoint.getSignature());
return joinPoint.proceed();
} finally {
log.info("结束分布式调用: {}", joinPoint.getSignature());
MDC.remove("traceId");
}
}
}
7.2 服务熔断降级策略
@Aspect
@Component
public class CircuitBreakerAspect {
private final Map<String, CircuitBreaker> breakers = new ConcurrentHashMap<>();
@Around("@annotation(circuitBreaker)")
public Object circuitBreaker(ProceedingJoinPoint joinPoint,
CircuitBreaker circuitBreaker) throws Throwable {
String key = joinPoint.getSignature().toLongString();
CircuitBreaker breaker = breakers.computeIfAbsent(key,
k -> new CircuitBreaker(
circuitBreaker.failureThreshold(),
circuitBreaker.waitDuration()
));
if (!breaker.allowExecution()) {
return fallbackMethod(joinPoint);
}
try {
Object result = joinPoint.proceed();
breaker.recordSuccess();
return result;
} catch (Exception e) {
breaker.recordFailure();
throw e;
}
}
private Object fallbackMethod(ProceedingJoinPoint joinPoint) {
// 实现降级逻辑
}
}
(注:实际实现中需要包含CircuitBreaker类的具体实现,此处为示例代码)
正文到此结束
相关文章
热门推荐
评论插件初始化中...