ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

什么是SpringAOP编程?一文帮助你快速理解!

什么是SpringAOP编程?一文帮助你快速理解! 前言为什么会需要 AOP 技术假设有这样的一个需求在某一天你的 leader 跟你说现在项目的线上接口响应速度越来越慢了要求你给所有的 Controller 加一个耗时监控。然后你打开项目一看足足有好几十个 Controller几百个接口如果一个个全部手动添加将会耗费你的大量时间负责检查代码人也会头疼无比。而这个时候就是 AOP 技术大显身手的时候了。AOP 全称 Aspect Oriented Programming 即面向切面编程它的核心思想就是——把那些“到处都要用但又跟实际业务没有什么关系”的代码或者说功能如日志、监控等抽出来统一处理。体现在上面的需求就是你只要开发一个耗时监控代码再通过一些注解和配置就可以一次性使它在所有的几百个接口处生效而不用一个个的去复制粘贴。或者举一个生活上的例子小区物业的门禁系统。每个小区的住户回家都要开门业务方法的执行但保安AOP 切面会在你开们前检查你是不是这个小区的Before 前置校验你开门进去后业务方法执行完毕保安可能会在记录本上登记一下After 后置日志。从整个流程上看保安做的事跟“开门回家”这件事本身没有关系但每个小区的人想“开门回家”都要经过这些。“这些”就是 AOP 干的事。AOP 相关核心术语AOP 相关的核心术语主要有五个连接点JoinPoint、切点Pointcut、通知Advice、切面Aspect、织入Weaving看完整篇内容后你可以尝试只凭这几个词看能不能把他们的含义直接给背出来。下面具体讲讲各自的含义。连接点 JointPoint这个指的是程序运行过程中所有可以被切入的位置而在 Spring AOP 中基本上就是所有的方法调用。注意是所有切点 Pointcut切点就是你从上面的所有连接点中所挑出的你真正想切入的那一批。而这是用表达式来定义的比如“所有的带有RestController 的类里的所有方法”。通知 Advice通知就是你想要在切点所具体要做的事。在 Spring AOP 中提供了五种通知类型方便你根据情况进行选择如可以选择在目标方法之前执行通知、在目标方法之后执行通知等。这里不方便赘述后面会在 AOP 常用注解处进行叙述。切面 Aspect切面就是把上面的东西进行打包而成的一个模块通常就是一个用Aspect 标记的一个 Java 类就是 PointcutAdviceAspect织入 Weaving织入就是指把切面逻辑具体应用到目标对象、生成代理对象的过程。这个过程是由 Spring 负责Spring AOP 是在运行时通过动态代理完成织入的。一个 AOP 实践示例接下来我将展示一个 AOP 的实践示例(以我的实验项目为例示例统一日志打印切面先在 pom.xml 文件导入依赖!--Spring AOP 支持 - 提供面向切面编程能力 包含AspectJ 织入框架、Spring AOP 代理、Aspect/Pointcut/Around 等注解支持 用途日志切面、性能监控、权限校验、事务管理等横切关注点-- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-aop/artifactId /dependency创建切面类Aspect // 标记这是一个切面类 Component // 注册为 Spring Bean由 Spring 容器管理 Slf4j // Lombok 注解自动生成 log 对象 public class RequestLogAspect { /** * 【切点定义】—— 定义在哪里切入 * execution() 是最常用的切点表达式语法为 * execution(返回类型 包名.类名.方法名(参数类型)) * 下面这条表达式的含义 * public * → 匹配所有 public 方法任意返回类型 * com.tsz.myblog.controller..* → controller 包及其子包下的所有类 * .*(..) → 任意方法名任意参数列表 */ Pointcut(execution(public * com.tsz.myblog.controller..*.*(..))) public void requestLogPointcut() { // 切点方法本身不需要任何代码它只是一个标记 // 后面的通知通过引用这个方法名来使用这个切点 } /** * 【环绕通知】—— 最强大的通知类型可以控制目标方法是否执行 * 环绕通知的执行流程 * 请求进入 → ①前置日志 → ②调用目标方法 → ③后置日志 → 返回响应 * ↓ 如果异常 * ④异常日志 * ProceedingJoinPoint 是连接点的增强版提供了 proceed() 方法来执行目标方法 */ Around(requestLogPointcut())//表示该通知应用的切点是requestLogPointcut() public Object around(ProceedingJoinPoint joinPoint) throws Throwable { // ① 前置阶段收集请求信息 // 获取当前 HTTP 请求对象Spring 将请求绑定在当前线程上下文中 ServletRequestAttributes attributes (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); // 如果不在 HTTP 请求上下文中比如定时任务触发直接放行 if (attributes null) { return joinPoint.proceed(); } HttpServletRequest request attributes.getRequest(); // 获取目标方法的签名信息 // joinPoint.getSignature() → 方法签名如 Result createBlog(CreateBlogDTO) // .getDeclaringTypeName() → 所在类的全限定名 // .getName() → 方法名 String classMethod joinPoint.getSignature().getDeclaringTypeName() . joinPoint.getSignature().getName(); String httpMethod request.getMethod(); // GET / POST / DELETE ... String uri request.getRequestURI(); // 请求路径 String ip request.getRemoteAddr(); // 客户端 IP Object[] args joinPoint.getArgs(); // 方法入参数组 // 打印请求开始日志 log.info(HTTP方法 : {} {}, httpMethod, uri); log.info(IP地址 : {}, ip); log.info(目标方法 : {}, classMethod); log.info(请求参数 : {}, Arrays.toString(args)); // ② 执行目标方法并计时 long startTime System.currentTimeMillis(); Object result null; try { // ⭐ 核心调用目标方法获取返回值 // 如果不调用 proceed()目标方法将不会执行 result joinPoint.proceed(); // ③ 后置阶段方法正常返回 long duration System.currentTimeMillis() - startTime; log.info(目标方法 : {}, classMethod); log.info(耗时 : {} ms, duration); log.info(返回结果 : {}, result); return result; } catch (Throwable e) { // ④ 异常阶段方法抛出异常 long duration System.currentTimeMillis() - startTime; log.error(请求异常); log.error(目标方法 : {}, classMethod); log.error(耗时 : {} ms, duration); log.error(异常类型 : {}, e.getClass().getSimpleName()); log.error(异常信息 : {}, e.getMessage()); // ⚠️ 捕获异常后必须重新抛出否则异常会被吞掉 throw e; } } }使用结果当我在项目的测试接口输入 TSZ 并点击发送如后端得到的日志如下成功显示了该请求所对应的 HTTP 类型、URL、IP、目标方法、请求参数、对应方法所耗时间等信息同时在 Controller 包下的所有 public 方法都会返回这些日志信息于是基本解决了开头所提到的那个需求。AOP 常用注解下面就来讲讲 AOP 的一些常用注解首先是早就提到了的通知类型类型执行时机典型场景Before目标方法之前参数校验、权限检查After目标方法之后不管成功失败资源清理类似finallyAfterReturning目标方法正常返回后记录返回值、数据脱敏AfterThrowing目标方法抛出异常后异常报警、回滚Around包裹目标方法前后都能干性能监控、事务管理其它注解Aspect示例中提到过标记该类是一个切面类方便 Spring 容器调用Pointcut用来定义切入点表达式表达式即确认对应通知织入哪些方法中去。注意切入点表达式有许多种写法值得去学习了解一下这里不便叙述。Order用来定义切面的执行顺序值越小优先级越高适用于有多个切面作用于同一个连接点时使用Order(1/2/……EnableAspectJAutoProxy在配置类上是使用用于启动基于注解的 AOP 代理功能但目前常用的一个复合注解SpringBootApplication 同样包含其功能注意事项一不要滥用 AOPAOP 是一个好用的技术但不要什么都往 AOP 里塞。一条使用 AOP 的法则AOP适合做“跟业务无关但到处都要用”的事——日志、鉴权、限流、监控、缓存。不适合做“跟业务强相关”的事二切入点表达式不要包含太大范围尽量精确定位要切入的点因为范围太大会严重影响性能并且不好修改三要根据具体情况选择合适的表达式类型以提高性能本文没讲表达式的书写结语AOP 的入门其实不难真正难的是如何去写一个好的“通知 Advice”这方面的学习可以通过去阅读别人写的好的通知代码。同时要写好切入点表达式不要图方便就直接写一个execution(*.(..))这种全局匹配最后再讲一遍AOP 全称 Aspect Oriented Programming 即面向切面编程它的核心思想就是——把那些“到处都要用但又跟实际业务没有什么关系”的代码或者说功能如日志、监控等抽出来统一处理。如果本文对你有帮助不妨点赞收藏支持一下吧
返回列表