ARTICLE DETAIL

资讯详情

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

Spring XML AOP实战:从动态代理到切点表达式,老项目维护必读指南

Spring XML AOP实战:从动态代理到切点表达式,老项目维护必读指南 这一篇Spring之旅是被一个老项目逼出来的。原本我的计划是直接开讲注解式AOP毕竟现在新项目里谁还会打开beans.xml写一大堆aop:config呢可前阵子接手了一个七年前的老系统核心服务的操作日志、权限校验、参数加密全部是通过XML AOP织进去的。我盯着那几百行的beans.xml加上各种aop:aspect的ref引用整整理了一下午才把切面关系网搞清楚。后面我花了一周时间把Spring基于XML的AOP开发从配置文件到代理生成链路认真捋了一遍。这篇就当是整理出来的学习笔记不管你是刚接触Spring的新手还是被派去维护老项目的“接盘侠”应该都能少走一些弯路。1. 为什么现在还要学XML方式的AOP老项目维护带来的真实教训1.1 接手老项目几百行beans.xml给我的冲击我去看那个老系统的时候光打开applicationContext.xml就有点懵。bean节点倒还好说真正让人头皮发麻的是后半段那一大坨aop:config。一个服务模块配一个aop:aspect里面挂了前置通知、环绕通知、异常通知业务日志和权限切面还拆成了两个独立文件引入。这种配置方式最大的问题是切面的逻辑散落在AOP相关的事实在代码里根本看不见。你翻开UserService的源码方法干干净净但实际上每次调用都在被外层代理拦截。如果你不知道beans.xml里配了哪些切点表达式排查问题时压根不会往那个方向想。我当时就吃过这个亏一个接口响应莫名多出几毫秒查了半天数据库慢查询最后才发现是一个环绕通知在每次方法执行前做了一次远程调用。所以我的第一个建议是接手任何老项目第一件事先把所有XML配置里的aop:config全部扫一遍把所有切点表达式列成清单。这比先看业务代码重要得多。1.2 AOP原理动态代理是理解一切配置的钥匙不管用XML还是注解Spring AOP的底层原理都是同一个动态代理。理解不了动态代理学AOP配置就只是在背标签。Spring AOP在运行时会为目标对象生成一个代理对象。调用方拿着的是代理对象真正执行业务方法之前代理对象会先按“通知链”的顺序把各个切面逻辑执行一遍再决定是否调用真实目标方法。有两种代理方式JDK动态代理目标类实现了接口。Spring基于java.lang.reflect.Proxy生成一个实现同一接口的代理类调用时通过InvocationHandler拦截。CGLIB代理目标类没有实现接口。Spring用CGLIB生成目标类的子类通过继承来覆盖方法从而植入切面逻辑。Spring在这两者之间是自动选择的。默认规则是目标类实现了任何一个接口就用JDK动态代理一个接口都没有才退回到CGLIB。如果想让Spring无条件使用CGLIB可以在aop:config上加proxy-target-classtrue。理解了这一点后面好多坑就能看明白了。比如没有接口的类Spring生成的代理对象和原类不是同一个对象如果你在代码里用instanceof去判断或者强转成某个具体类有时候会得到一些匪夷所思的结果本质上就是代理对象和原始对象的类结构不一样。1.3 横切关注点AOP到底解决了什么问题在没有AOP之前解决日志、事务、权限这类“横切关注点”问题的标准姿势是在每个方法里重复写同样的代码。比如你要给100个service方法打印操作日志老老实实写的话每个方法开头两三行日志代码结尾又两三行出了异常再写一段。项目刚做出来没问题等需求改成“日志里必须带上操作人ID”的时候你就得改100个地方的200多行代码漏改一个就会出线上事故。AOP的思路是把这些逻辑抽出来放到一个独立的模块里然后通过“切点”声明哪些方法要套上这些逻辑。改需求的时候只改切面类业务方法一行不动。这样做的好处有三个业务代码更干净关注点单一。横切逻辑复用同一套日志、事务逻辑可以挂到任何业务方法上。逻辑统一收口需求变更时只改一个地方。AOP的典型使用场景我列一下操作日志记录、事务管理、权限校验、接口限流、参数校验与脱敏、性能监控、缓存处理。我这些年在实际项目里基本就用这几个场景老系统里那个操作日志切面就是最典型的XML AOP应用。2. XML里的AOP配置和AOP核心概念怎么对应2.1 六大术语对照表切面、切点、通知、连接点、目标、织入很多初学者看AOP术语容易昏头我建议直接把这几个概念和XML标签一一对应起来记。概念含义XML中的对应Aspect切面一组“横切逻辑要织入的位置”的集合aop:aspect ref切面BeanPointcut切入点一个表达式定义“哪些方法会被拦截”aop:pointcut expressionexecution(...)Advice通知具体的横切逻辑比如记录日志、开启事务aop:before、aop:around等子标签JoinPoint连接点被拦截到的某一次具体方法调用运行时概念在切面方法参数里体现Target目标对象被切面横切的原业务对象bean idaccountServiceWeaving织入把切面逻辑应用到目标方法并生成代理的过程aop:config整体做的事情记忆方法很简单切面是一个“盒子”盒子里面装着“通知”通知负责干活“切点”决定了盒子上的哪几根“针”会扎到目标方法上。目标是针扎的对象织入是扎针这个动作连接点则是针尖落下去的那一个具体位置。2.2 一个最小可用的aop:config骨架先看一个最精简的XML AOP配置后面所有内容都围绕这个骨架展开?xml version1.0 encodingUTF-8? beans xmlnshttp://www.springframework.org/schema/beans xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xmlns:aophttp://www.springframework.org/schema/aop xsi:schemaLocation http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd bean idaccountService classcom.example.service.AccountService/ bean idlogAspect classcom.example.aspect.LogAspect/ aop:config aop:aspect reflogAspect aop:pointcut idservicePointcut expressionexecution(* com.example.service.*.*(..))/ aop:before methodlogBefore pointcut-refservicePointcut/ /aop:aspect /aop:config /beansaop:config是根节点里面直接子节点有三种aop:pointcut、aop:advisor、aop:aspect。这三者在实际项目中的使用频率aop:aspect用来定义基于普通POJO的切面aop:advisor适配那些已经实现了Spring通知接口的类常见于事务配置而aop:pointcut既可以在aop:aspect内部定义也可以定义在aop:config顶层然后被多个aspect引用。2.3 公共切点的两种定义方式与作用域切点定义的位置不同能被谁引用就不一样。在aop:config顶层定义的切点属于“全局切点”能被当前aop:config下所有aop:aspect引用。如果切点表达式被多个切面复用建议放顶层。在aop:aspect内部定义的切点作用域只在当前切面内可见其他切面引用不到。如果你两个切面对同一批方法织入不同逻辑就得在每个aspect里单独定义一份切点或者干脆在顶层统一定义。我见过别人把这两个场景混在一起用结果在某个aspect里引用了另一个aspect的私有点cutpoint启动直接报错。所以记一条规则切点定义在哪里就在哪里作用域可见跨aspect引用必须用顶层切点。另外提醒一句aop:config可以有多个一个配置文件里写多段aop:config也是允许的但同一段aop:config下的aspect会按声明顺序参与代理构建。切面优先级这事儿放到后面第7节讲。3. 从零搭一个基于XML的AOP案例3.1 工程依赖怎么配pom.xml里少了这个包一定会后悔搭建一个最小的Spring XML AOP项目Maven依赖只需要两个核心包dependencies dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version5.3.30/version /dependency dependency groupIdorg.aspectj/groupId artifactIdaspectjweaver/artifactId version1.9.19/version /dependency /dependenciesspring-context会传递引入spring-core、spring-beans、spring-aop这些基础模块。而aspectjweaver是用来解析AspectJ切点表达式的没有它aop:config里的expressionexecution(...)在运行时就会因为找不到表达式解析器而异常。另外提醒一下如果你图省事直接加spring-boot-starter-aop它会把aspectjweaver带进来但那种方式默认走自动配置跟今天我们纯XML的方式不完全是一回事。做纯XML项目老老实实把这两个依赖写清楚就行。3.2 写业务类和切面类先创建业务类这里用一个简单的账户服务。注意我故意没有让它实现接口这样在Spring 5.x下默认走CGLIB代理后续在测试里能看到更明显的行为package com.example.service; public class AccountService { public void transfer(String from, String to, Double amount) { System.out.println(执行转账 from - to 金额 amount); } public Double queryBalance(String accountNo) { System.out.println(查询余额账户 accountNo); return 8888.00; } public void freezeAccount(String accountNo) { System.out.println(冻结账户 accountNo); throw new RuntimeException(账户余额不足冻结失败); } }再写切面类。这个类本身就是一个普通POJO里面每个方法对应一种通知逻辑方法签名有约定具体约定在XML配置里通过method属性指定package com.example.aspect; import org.aspectj.lang.ProceedingJoinPoint; public class LogAspect { public void beforeLog() { System.out.println([前置通知] 开启事务记录操作日志...); } public void afterReturningLog(Object result) { System.out.println([后置返回通知] 方法正常返回结果 result); } public void afterThrowingLog(Throwable ex) { System.out.println([异常通知] 方法抛出异常 ex.getMessage()); } public void afterLog() { System.out.println([最终通知] 清理资源关闭连接...); } public Object aroundLog(ProceedingJoinPoint joinPoint) throws Throwable { long start System.currentTimeMillis(); System.out.println([环绕通知] 方法开始 joinPoint.getSignature().getName()); Object result joinPoint.proceed(); System.out.println([环绕通知] 方法结束耗时 (System.currentTimeMillis() - start) ms); return result; } }这里先说两个很容易出错的方法签名细节afterReturningLog(Object result)的参数名必须和XML里returning属性指定的一致否则参数值注入不进去。afterThrowingLog(Throwable ex)的参数名必须和XML里throwing属性的一致否则异常信息拿不到。aroundLog的参数必须是ProceedingJoinPoint并且方法要声明throws Throwable因为在环绕通知里joinPoint.proceed()执行目标方法时什么异常都可能抛出来不能随意吞掉。3.3 关键来了beans.xml里的AOP配置把业务类和切面类都定义成Bean然后用aop:config把它们“锚”在一起?xml version1.0 encodingUTF-8? beans xmlnshttp://www.springframework.org/schema/beans xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xmlns:aophttp://www.springframework.org/schema/aop xsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd bean idaccountService classcom.example.service.AccountService/ bean idlogAspect classcom.example.aspect.LogAspect/ aop:config aop:pointcut idservicePointcut expressionexecution(* com.example.service.*.*(..))/ aop:aspect reflogAspect aop:before methodbeforeLog pointcut-refservicePointcut/ aop:after-returning methodafterReturningLog pointcut-refservicePointcut returningresult/ aop:after-throwing methodafterThrowingLog pointcut-refservicePointcut throwingex/ aop:after methodafterLog pointcut-refservicePointcut/ aop:around methodaroundLog pointcut-refservicePointcut/ /aop:aspect /aop:config /beans注意aop:aspect reflogAspect里面的ref引用的就是上面定义的那个切面Bean。method属性指向切面类里的方法名。pointcut-ref引用的是切点id。3.4 运行结果分析通知是怎么串起来的写个主方法跑一下正常路径package com.example; import com.example.service.AccountService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class MainTest { public static void main(String[] args) { ApplicationContext context new ClassPathXmlApplicationContext(beans.xml); AccountService service context.getBean(accountService, AccountService.class); service.transfer(张三, 李四, 1000.00); System.out.println(---- 分割线 ----); try { service.freezeAccount(622200001); } catch (RuntimeException e) { System.out.println(主程序捕获异常 e.getMessage()); } } }正常路径transfer()的输出如下[环绕通知] 方法开始transfer [前置通知] 开启事务记录操作日志... 执行转账张三 - 李四金额1000.0 [后置返回通知] 方法正常返回结果null [最终通知] 清理资源关闭连接... [环绕通知] 方法结束耗时5ms异常路径freezeAccount()的输出如下[环绕通知] 方法开始freezeAccount [前置通知] 开启事务记录操作日志... 冻结账户622200001 [异常通知] 方法抛出异常账户余额不足冻结失败 [最终通知] 清理资源关闭连接... 主程序捕获异常账户余额不足冻结失败从这个输出能看到三层结构环绕通知在最外层它包住了前置通知和后置通知前置通知在目标方法执行之前触发后置返回通知在正常返回后触发异常通知只会在方法抛异常时触发最终通知无论正常还是异常都会执行等价于finally块。4. 五种通知类型的XML写法、执行顺序与镜像理解4.1 五种通知XML配置与语义对照通知类型XML标签语义典型用途前置通知aop:before目标方法执行前触发权限校验、参数校验、开启事务后置返回通知aop:after-returning目标方法正常返回后触发记录成功日志、组装返回值异常通知aop:after-throwing目标方法抛出异常后触发异常告警、事务回滚最终通知aop:after目标方法结束后触发无论是否抛异常资源释放、连接关闭环绕通知aop:around完全包裹目标方法可自定义前后逻辑性能监控、分布式锁、事务模板aop:after-returning和aop:after很多人分不清。记住一个关键区别after-returning只在方法正常返回时执行方法抛异常它就不管了after等同于finally不管成功失败都会执行。这也是“正常返回”和“结束”两种语义的差别。aop:after-throwing里还有一个坑它的throwingex必须和方法参数名一致字符串对不上Spring启动时就报参数绑定错误。这个错误属于特别好排查的那种因为它会在启动阶段直接暴露。4.2 正常路径和异常路径的执行顺序实测还是以上面的输出为例我把执行顺序画成文本流程图正常路径的执行顺序环绕通知前段 - 前置通知 - 目标方法 - 后置返回通知 - 最终通知 环绕通知后段异常路径的执行顺序环绕通知前段 - 前置通知 - 目标方法抛出异常 - 异常通知 - 最终通知 环绕通知后段不执行异常继续往上抛需要特别说明的是我在Spring 5.3.x上实测得出的顺序是这样。同一个切面里挂了多个不同类型的通知时通知之间的精确顺序在不同版本下可能有细微差别因为这严格来说并没有在规范里被完全锁定。所以开发时尽量不要依赖“多个不同类型通知之间的相对顺序”而是把核心逻辑写进环绕通知里用try-catch-finally自己控制。4.3 around通知为什么能包住其他通知从调用链看执行环绕通知和其他通知有一个本质区别其他通知是在代理链的某一环上执行一次而环绕通知拥有ProceedingJoinPoint它可以决定“目标方法到底什么时候执行”甚至可以决定“目标方法还执不执行”。从调用链的角度看Spring AOP的代理对象内部维护了一条通知链。环绕通知在这条链上处于最外层anchor的位置它调用joinPoint.proceed()时代理链才继续往下走依次触发前置通知、目标方法、后置通知。如果环绕通知根本不调用proceed()那目标方法被“短路”了下面的前置通知也不会执行。这个特征非常有用。比如做接口限流你可以先判断令牌桶里有没有令牌没令牌直接返回一个包装好的“请求过于频繁”结果根本不调用proceed()。做权限校验也一样没有权限直接抛出异常或返回默认值目标方法就不用执行了。这也是为什么很多基于AOP的通用组件核心逻辑都写在环绕通知里。5. 切点表达式execution从语法到实战的完整拆解5.1 execution表达式的基本结构execution是Spring AOP用得最多、也最可靠的切点表达式它按“看得见的签名”来匹配方法。基本结构是execution(访问修饰符 返回类型 包名.类名.方法名(参数列表) 异常类型)实际写的时候修饰符和异常类型可以省略。比如我在例子里写的execution(* com.example.service.*.*(..))拆开就是*返回类型是任意的。com.example.service.*只匹配com.example.service这个包下的所有类不包含子包。.*匹配类中的任意方法名。(..)匹配任意参数列表。如果想匹配子包也能匹配上包的部分要写com.example.service..*两个点表示包含子包。5.2 高频写法与语义对照切点表达式匹配范围execution(public * com.example.service.*.*(..))service包下所有public方法execution(* com.example.service.UserService.*(..))指定类中的所有方法execution(* com.example..*.*(..))com.example及其所有子包中的所有方法execution(* com.example.service.*.get*(..))service包下所有以get开头的方法execution(* *(..))所有类的所有方法慎用execution(* com.example.service.OrderService.save*(Long, ..))save开头第一个参数是Long后面参数任意通配符的记忆口诀*只能匹配一层..能匹配多层。用在包路径里..表示子包用在参数列表里..表示任意类型、任意个数的参数单独一个*在参数列表里则表示恰好一个任意类型参数。5.3 切点匹配失败的典型场景切点表达式写错最麻烦的一点是Spring不会启动报错只会静默不匹配。你配置了半天写了个日志切面运行起来发现日志一条都没打第一反应往往是代码配错了而不是表达式写错了。我列几个高频翻车场景包名多写了子包。com.example.service.*匹配不到com.example.service.sub.xxx里的类因为单层通配符不包含子包。如果业务类实际在子包里必须改成com.example.service..*。类名多打了个Impl。老项目里ServiceImpl都是com.xxx.service.impl.UserServiceImpl这种路径但表达式写成了com.xxx.service.*.*(..)就匹配不上。返回类型写成了具体类型。接口方法返回ListUser你在表达式里写execution(* com.xxx.service.UserService.list(..))没问题但如果你试图匹配具体泛型比如Listcom.xxx.User泛型在运行时会被擦除基本都会翻车。(..)和(*)混用。(..)表示任意参数(*)表示恰好一个参数。有人想表达“任意参数”写成了明文的方法名加()结果只匹配无参方法业务方法全被跳过了。我的排查习惯是切点不生效时先把表达式简化到最小范围比如execution(* com.example.service.AccountService.transfer(..))确定能匹配上之后再逐步放宽条件。这样能很快定位是表达式的问题还是别的问题。6. XML AOP踩坑实录从启动报错到静默失效的排查链路6.1 启动就报错aop命名空间与依赖缺失第一类问题是Spring容器启动阶段直接抛异常这种其实还算友好因为它至少给了明确的错误信息。最常见的两种The matching wildcard is strict, but no declaration can be found...或者org.xml.sax.SAXParseException。十有八九是XML里用了xmlns:aop和aop:前缀但schemaLocation里没有写对应的spring-aop.xsd。解决办法就是把第一节里那段xmlns:aop和schemaLocation原样抄进去。java.lang.NoClassDefFoundError: org/aspectj/util/PartialOrder$PartialOrderWrapper。这就是前面说的aspectjweaver依赖缺失。Spring容器里加载aop:config时解析切点表达式需要AspectJ的类没有它必然挂。遇到这类问题我的处理顺序是先检查XML头部的命名空间声明再检查schemaLocation里的URL路径是否和spring-aop.xsd一致最后看pom.xml里aspectjweaver有没有进来。三步走完基本能解决。6.2 最隐蔽的坑切点静默失效第二类问题最坑人启动全程无异常但通知就是不执行。遇到这种问题我第一步做的是先确认Bean是不是被代理了。在测试代码里打印service.getClass()如果是com.example.service.AccountService说明Bean就是原始对象代理根本没生成如果是AccountService$$EnhancerBySpringCGLIB说明代理生成了那就是切点表达式没匹配上。这个区分非常关键。它会直接把问题一半对一半分到两条排查路线上Bean是原始对象说明aop:config没有生效去检查aop:aspect reflogAspect的ref是不是写错了Bean名或者这个Bean根本没有被Spring扫描到。Bean是代理对象但通知没执行直接怀疑切点表达式用上一节说的缩小范围法去试。另外还有一种隐蔽情况aop:config里定义了切点但aop:aspect里的通知标签写的是pointcut而不是pointcut-ref。如果用的是后者切点必须定义在当前aspect内部如果用了pointcut-ref引用了一个不存在的id启动时会报错这个倒不会静默。6.3 自调用问题同一个类里的方法调用为什么绕过了代理这是我见过最经典的一个AOP失效场景。public class AccountService { public void transfer(String from, String to, Double amount) { System.out.println(执行转账...); this.updateBalance(张三, 1000.00); } public void updateBalance(String accountNo, Double amount) { System.out.println(更新余额...); } }如果切点表达式是execution(* com.example.service.AccountService.*(..))理论上两个方法应该都会被拦截。但实测你会发现transfer被拦截了里面调的updateBalance没有被拦截。因为在transfer方法内部this指向的是当前执行的对象也就是目标对象本身而不是Spring创建的那个代理对象。代理对象虽然包裹着目标对象但目标对象内部调用自己的另一个方法时并没有走代理的入口。解决自调用问题的常规姿势有三个拆分两个类把互相调用的方法放到不同Bean里这样调用别的Bean时会经过代理。通过AopContext.currentProxy()拿到当前代理对象然后调用代理对象的方法但aop:config上需要设置expose-proxytrue得当心线程无关性问题。把目标方法都放到事务、日志之外的地方业务层调用时自己注意方法是入口方法还是内部方法。我在维护老项目时遇到过这个问题当时的代价就是一个库存扣减操作里“扣减”方法没触发日志切面导致线上数据异常时连个操作记录都没有。所以如果你在设计接口时知道某个类的某些方法会被切面拦截内部互相调用要格外小心。6.4 顺手聊一下XML文件怎么打开和编辑既然这篇文章主题是XML顺带说一个新手经常会问的细节XML文件到底怎么打开和编辑。不要用记事本打开XML然后硬改缩进一乱就看不清结构了而且编码极容易出问题。Windows上我建议直接用VS Code或者IDEAIDEA对Spring XML有专门的语法提示和命名空间校验。打开一个beans.xml的时候IDEA会帮你识别xmlns:aop并提示是否有未声明的标签。没有IDE环境的时候也可以用Chrome直接拖动XML文件进去预览或者用在线XML格式化工具快速整理结构。但涉及Spring配置修改永远记住一条在IDE里改完再用mvn test或启动容器验证别改完就上生产。7. XML AOP和注解AOP怎么选我的取舍建议7.1 两种方式横向对比我自己的态度是新项目能上注解就上注解老项目里的XML配置除非要动那一块逻辑否则尽量别去大改。这两种方式各有清晰的适用场景维度XML AOP注解AOP配置集中度所有切面集中在一个或几个XML文件里切面散布在各自的Java类上可读性切面关系和全局扫描清晰但业务代码看不出切面痕迹直接看注解就知道哪个方法被切了开发效率低每次新增切面要改XML高一个Aspect类搞定类型安全弱方法名用字符串写错运行时才知道相对强IDE能提示方法签名适合场景老项目维护、切面需要动态调整、第三方类无法加注解新项目、团队成员对注解熟悉切点复用顶层pointcut可被多个aspect引用通过Pointcut方法复用注解AOP在代码里能直接看到Before、Around查问题时顺着注解就能找到切面类。XML AOP的优点则在于所有拦截规则集中在一个配置文件里对运维和架构审计更友好生产环境排查时可以只开一个XML文件看全貌不用翻遍整个项目找注解。这两种思路没有绝对的对错纯粹是取舍。7.2 advisor与aspect的区别事务配置里的老朋友讲到XML AOP还有一个概念必须提那就是aop:advisor。advisor在Spring里是一个更古老的抽象它等于“一条通知一个切点”的最小组合。在XML配置里aop:advisor通常和tx:advice搭配使用实现声明式事务bean idtransactionManager classorg.springframework.jdbc.datasource.DataSourceTransactionManager property namedataSource refdataSource/ /bean tx:advice idtxAdvice transaction-managertransactionManager tx:attributes tx:method namesave* propagationREQUIRED/ tx:method nameupdate* propagationREQUIRED/ tx:method namefind* read-onlytrue/ /tx:attributes /tx:advice aop:config aop:advisor advice-reftxAdvice pointcutexecution(* com.example.service.*.*(..))/ /aop:configadvisor和aspect最核心的区别是advisor只能有一个通知和一个切点而aspect可以挂多个通知。事务这种只需要一套切面逻辑的场景用advisor正合适。你要是想在一个aspect里既做日志又做权限还做事务那就用aspect。7.3 一个过来人的选择思路最后给个实际参考。第一简历上写“熟悉Spring AOP”不能光会Around一种写法。你至少要能说出XML AOP里的aop:config、aop:aspect、五种通知标签的语义区别讲清楚advisor和aspect的差别最好能现场写一个简单的execution切点表达式。第二接手老项目优先把XML AOP里的切点表达式全部摘出来形成文档标注每个切面对应哪几个业务方法。这些切点表达式就是老系统的“隐形地图”不画出来后面每个改动都像在盲人摸象。第三新项目如果不涉及特殊要求直接注解AOP开发效率高很多。但如果是对外发布的中间件、基础组件XML方式反而更容易和不同业务方对接。在写这一篇的过程里我又翻了翻spring-aop.xsd的标签定义愈发觉得XMI配置虽然啰嗦但对理解AOP的完整链路特别有帮助你被迫把每一个概念都想清楚才能在XML里准确写出对应的标签。把这一块啃下来之后再回头用注解AOP会顺手得多。老项目的经验不白踩这些坑填平之后后面的Spring学习之路反而更顺了。
返回列表