当前位置: 首页 > news >正文

Spring AOP深度解析

Spring AOP深度解析

引言

面向切面编程(Aspect-Oriented Programming,AOP)是Spring框架的核心功能之一,它允许开发者将横切关注点(如日志、事务、安全)与业务逻辑分离,提高代码的模块化程度。本文将深入剖析Spring AOP的底层实现、核心概念、通知类型、切点表达式以及在Spring Boot中的最佳实践。

一、AOP核心概念

1.1 基本术语

Join Point(连接点):程序执行的某个位置,Spring AOP中只有方法调用连接点;Pointcut(切点):用于匹配连接点的表达式,决定哪些连接点被拦截;Advice(通知):切面在连接点执行的代码,包括前置通知、后置通知等;Aspect(切面):通知和切点的组合;Weaving(织入):将切面应用到目标对象的过程。

1.2 Spring AOP vs AspectJ

Spring AOP只支持方法级别的拦截,采用代理模式实现;AspectJ支持字段、构造函数等更细粒度的拦截,采用编译时或加载时织入。

二、通知类型

2.1 前置通知

@Aspect @Component public class LoggingAspect { @Before("execution(* com.example.service.*.*(..))") public void before(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); System.out.println("Before: " + methodName + " with args: " + Arrays.toString(args)); } }

2.2 后置通知

@Aspect @Component public class PerformanceAspect { @AfterReturning( pointcut = "execution(* com.example.service.*.*(..))", returning = "result" ) public void afterReturning(JoinPoint joinPoint, Object result) { String methodName = joinPoint.getSignature().getName(); System.out.println("After returning: " + methodName + " result: " + result); } @AfterThrowing( pointcut = "execution(* com.example.service.*.*(..))", throwing = "exception" ) public void afterThrowing(JoinPoint joinPoint, Exception exception) { String methodName = joinPoint.getSignature().getName(); System.err.println("Exception in: " + methodName + " message: " + exception.getMessage()); } }

2.3 环绕通知

@Aspect @Component public class TransactionAspect { @Autowired private PlatformTransactionManager transactionManager; @Around("execution(* com.example.service.*.*(..))") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { String methodName = joinPoint.getSignature().getName(); TransactionStatus status = transactionManager .getTransaction(new DefaultTransactionDefinition()); try { Object result = joinPoint.proceed(); transactionManager.commit(status); return result; } catch (Exception e) { transactionManager.rollback(status); throw e; } } }

三、切点表达式

3.1 常用表达式

// 方法签名匹配 execution(public * com.example.service.UserService.get*(..)) // 公共方法 execution(* com.example.service.*.*(..)) // service包下所有方法 execution(* com.example.service..*(..)) // service及其子包 // 参数匹配 execution(* *(String, Integer)) // 两个参数 execution(* *(String, ..)) // 第一个参数String execution(* com.example.service.UserService+.*(..)) // 包括子类 // 注解匹配 @annotation(org.springframework.stereotype.Service) // 带@Service注解 @args(com.example.annotation.Transactional) // 带@Transactional参数

3.2 自定义切点

@Aspect @Component public class CustomPointcuts { @Pointcut("execution(* com.example.service.*.*(..))") public void serviceLayer() {} @Pointcut("execution(* com.example.repository.*.*(..))") public void repositoryLayer() {} @Pointcut("serviceLayer() || repositoryLayer()") public void applicationLayer() {} @Pointcut("within(com.example.service.*)") public void inServicePackage() {} }

四、切面优先级

4.1 @Order注解

@Aspect @Order(1) @Component public class SecurityAspect { // 最高优先级 } @Aspect @Order(2) @Component public class LoggingAspect { // 次高优先级 } @Aspect @Order(Ordered.LOWEST_PRECEDENCE) @Component public class PerformanceAspect { // 最低优先级 }

五、Spring Boot集成

5.1 启用AOP

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
spring: aop: auto: true proxy-target-class: true expose-proxy: true

5.2 自定义注解切面

@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Retryable { int maxAttempts() default 3; long delay() default 1000; Class<? extends Throwable>[] value() default {}; } @Aspect @Component public class RetryAspect { @Around("@annotation(retryable)") public Object around(ProceedingJoinPoint joinPoint, Retryable retryable) throws Throwable { int attempts = retryable.maxAttempts(); long delay = retryable.delay(); Class<? extends Throwable>[] exceptions = retryable.value(); for (int i = 0; i < attempts; i++) { try { return joinPoint.proceed(); } catch (Throwable e) { if (i == attempts - 1 || !isRetryable(e, exceptions)) { throw e; } Thread.sleep(delay); } } throw new RuntimeException("Should not reach here"); } private boolean isRetryable(Throwable e, Class<? extends Throwable>[] exceptions) { for (Class<? extends Throwable> ex : exceptions) { if (ex.isInstance(e)) { return true; } } return false; } }

六、JoinPoint详解

6.1 获取信息

@Aspect @Component public class JoinPointAspect { @Before("execution(* com.example.service.*.*(..))") public void before(JoinPoint joinPoint) { // 获取方法签名 Signature signature = joinPoint.getSignature(); String className = signature.getDeclaringTypeName(); String methodName = signature.getName(); // 获取参数 Object[] args = joinPoint.getArgs(); // 获取目标对象 Object target = joinPoint.getTarget(); // 获取代理对象 Object proxy = joinPoint.getThis(); // 获取方法参数名 MethodSignature ms = (MethodSignature) signature; String[] paramNames = ms.getParameterNames(); System.out.println(className + "." + methodName); } }

6.2 MethodSignature

@Aspect @Component public class MethodSignatureAspect { @Before("execution(* com.example.service.*.*(..))") public void signatureInfo(JoinPoint joinPoint) { MethodSignature signature = (MethodSignature) joinPoint .getSignature(); // 返回类型 Class<?> returnType = signature.getReturnType(); // 参数类型 Type[] parameterTypes = signature.getParameterTypes(); // 参数名 String[] parameterNames = signature.getParameterNames(); // 异常类型 Class<?>[] exceptionTypes = signature.getExceptionTypes(); } }

七、织入方式

7.1 JDK动态代理

// 基于接口的代理 public class JdkProxyExample { public static <T> T createProxy(T target, Class<T> interfaceClass) { return (T) Proxy.newProxyInstance( interfaceClass.getClassLoader(), new Class[]{interfaceClass}, (proxy, method, args) -> { System.out.println("Before: " + method.getName()); Object result = method.invoke(target, args); System.out.println("After: " + method.getName()); return result; } ); } }

7.2 CGLIB代理

// 基于类的代理 public class CglibProxyExample { public static <T> T createProxy(T target) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(target.getClass()); enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> { System.out.println("Before: " + method.getName()); Object result = proxy.invokeSuper(obj, args); System.out.println("After: " + method.getName()); return result; }); return (T) enhancer.create(); } }

八、最佳实践

8.1 避免切面循环依赖

// 错误:切面依赖注入自身 @Aspect @Component public class BadAspect { @Autowired private BadAspect self; // 错误 @Around("execution(* *.method(..))") public Object around(ProceedingJoinPoint pjp) { return self.proceed(pjp); } }

8.2 切面中的事务

@Aspect @Component @Order(1) // 在事务切面之前执行 public class LoggingAspect { @Autowired private AuditService auditService; @Around("execution(* com.example.service.*.*(..))") public Object log(ProceedingJoinPoint pjp) throws Throwable { String action = pjp.getSignature().getName(); Object[] args = pjp.getArgs(); try { Object result = pjp.proceed(); auditService.logSuccess(action, args); return result; } catch (Exception e) { auditService.logFailure(action, args, e); throw e; } } }

总结

Spring AOP是处理横切关注点的强大工具,通过切面可以将日志、事务、安全等功能与业务逻辑解耦。理解AOP的核心概念、切点表达式和各种通知类型,是正确使用AOP的基础。在实际应用中,应该合理设计切面,避免过度使用导致性能问题和代码复杂度增加。

http://www.cnnetsun.cn/news/2443207.html

相关文章:

  • NotebookLM实时协同黑科技:3个隐藏API+2个Chrome插件,让跨角色协作响应提速83%
  • 重新定义视频学习:Bili2Text如何将B站内容转化为结构化知识库
  • 魔兽争霸III终极兼容性增强插件:WarcraftHelper完整指南
  • 惠普游戏本性能解放:OmenSuperHub开源工具深度解析与实战指南
  • 关于变量赋值失败,yn有话说
  • 你的小米路由器安全吗?聊聊Nginx配置不当那些事儿(附自查清单)
  • 期刊论文发表提速:虎贲等考 AI,让核心期刊写作更规范、更高效、更容易中稿
  • 自动增益控制与灵敏度时间控制:从原理到工程实践
  • FreeRTOS SMP多核调试踩坑记:在TC397上如何确认你的任务真的跑在了对的CPU核心?
  • 如何用GrasscutterCommandGenerator轻松管理原神私服?新手快速入门指南
  • 如何用Highlighter打造永不消失的网页标记:终极网页高亮工具使用指南
  • Unity游戏自动翻译终极指南:XUnity.AutoTranslator完整教程 [特殊字符][特殊字符]
  • vue基于springboot框架的医疗健康管理平台
  • Python实现编译器前端:从词法分析到LLVM IR生成全解析
  • Linux代理连接链路稳定性治理方法
  • vue基于springboot框架的学生公寓宿舍管理系统
  • 相对路径的作用与价值
  • 游戏修改不求人:用Cheat Engine 7.4中文版,5分钟搞定《植物大战僵尸》阳光值
  • 基于MCP协议构建AI代理数据网关:从原理到项目分析服务器实战
  • 基于Git的个人代码片段库:高效管理与复用开发资产
  • 构建个人代码片段管理系统:从设计到实践
  • vue基于springboot框架的影视资源在线观看管理系统设计与实现
  • 从手机到桌面:APK Installer如何重新定义Windows上的Android应用体验
  • 终极指南:如何用STDF Viewer轻松解析半导体测试数据
  • Claude Code 用户如何通过 Taotoken 配置稳定可用的编程助手环境
  • AI YIGOU 电动行李箱智能功率 MOSFET 完整选型方案
  • 13 移动端 WEB 前端 WEB 开发 HTML5 + CSS3 + 移动 WEB
  • LightMem:大模型记忆增强框架,实现RAG到智能体的关键跨越
  • TVA动态批处理保延迟低于100ms
  • Google MaxText开源项目解析:JAX大模型训练框架与3D并行策略实践