Spring AOP配置原理:Advisor与XML方式及动态代理机制全解析

发布时间:2026/9/29 17:00:30
Spring AOP配置原理:Advisor与XML方式及动态代理机制全解析 配置AOP这事很多人的第一反应是看一眼配置能用就行。但一旦涉及复杂切点、多个通知顺序、或者不同代理方式的选择时光是“能用”就不够了。你迟早会遇到“明明配置没问题但切面就是不生效”这种问题然后被迫去翻Spring源码。与其到时候现查不如一开始就把advisor方式和XML方式这两条主流配置路线的原理理清楚。这篇文章不聊泛泛的概念直接从“配置背后到底发生了什么”这个角度切入说说两种方式的核心差异、运行原理、底层代理机制以及在实际项目中怎么选、怎么排雷。适合刚学Spring AOP正被各种名词绕晕的新人也适合写过不少切面但没深究过内部机制的开发者查漏补缺看完能实打实解决配置疑惑。1. 两种配置方式的整体认知它们到底在配置什么东西先说个基础判断advisor和XML配置AOP本质上做的是同一件事就是“把切点匹配规则和通知逻辑绑定起来生成代理对象”。区别在于绑定的方式不同一个是直接用Spring的Advisor对象体系来组织另一个是通过XML标签让Spring框架帮你在背后组装出这套体系。1.1 先搞懂AOP里的三个基础角色任何AOP配置不管写法多花哨都绕不开三个基础概念切点Pointcut通知Advice切面Aspect。切点负责回答“在哪里切入”它定义的是“哪些类的哪些方法需要被拦截”。注意这里包含两个维度一个是类级别的匹配比如com.example.service.*一个是方法级别的匹配比如public * com.example.service.*.*(..)里面的方法名和参数匹配。这两个维度是独立存在的Spring里面分别由ClassFilter和MethodMatcher两个子接口来负责。通知负责回答“切入之后干什么”它定义了额外的逻辑。Spring里通知有五种类型Before、AfterReturning、AfterThrowing、After最终、Around。其中Around是最特殊的它把整个方法调用包起来可以在前后都插入逻辑甚至可以完全替换原方法的执行。切面则是一个更高层的概念它把切点和通知组合起来形成一个完整的“规则动作”单元。在Spring AOP的发展历程里Aspect这个概念最初是从AspectJ那边借鉴过来的后来Spring自己的注解配置也沿用了这个名词。但在Spring内部真正存储配置信息的其实是Advisor接口。1.2 Spring AOP的运行链路从配置到代理对象Spring AOP的整个执行流程可以简化为三步解析配置、生成Advisor、创建代理。解析配置这一步就是把XML或注解里的信息读取出来。XML方式通过BeanDefinition解析器来处理aop:config标签注解方式通过AnnotationAwareAspectJAutoProxyCreator来扫描Aspect标注的类。这个环节输出的是一组Advisor候选对象。生成Advisor这一步比较关键。不管是哪种配置来源最终都会被统一转换成Advisor对象。Advisor接口定义很简单它就是“持有一个Pointcut和一个Advice的组合体”。所以整个Spring AOP在运行时根本不管你原来配置用的是aop:aspect标签还是Aspect注解也不管是Before还是aop:before统统都会被抽象成一个一个的Advisor来统一处理。创建代理这步是根据前面得到的Advisor列表判断目标类需要哪种代理方式然后生成代理对象。判断依据就是目标类是否实现了接口以及在配置里有没有强制指定proxy-target-class。这里涉及到JDK动态代理和CGLIB动态代理两种底层机制后面会单独展开讲。理解了这条链路就能明白一个重要的推论不管你用什么样的配置语法它在Spring运行时都是等价的。XML、注解、Advisor编程式配置殊途同归最终都汇入同一套代理机制。2. Advisor方式配置切面从接口到运行时背后的原理Advisor这个名词在Spring AOP里存在感很强但很多人对它的理解停留在“好像比Aspect低一层”。确实它是Spring内部更底层的抽象也是AOP配置在此刻真正操作的实体。2.1 Advisor为什么比Aspect更底层接口设计拆解看Advisor接口的设计能反映出Spring的设计思路。org.springframework.aop.Advisor只定义了一个方法getAdvice()用来返回通知对象。而大部分场景使用的PointcutAdvisor在此基础上扩展了两项getPointcut()返回切点对象isPerInstance()标记是否按实例创建。这个设计意味着什么意味着一个Advisor就是一个“孤立的切面单元”。它不需要知道别的Advisor的存在不需要感知全局配置只需要管好自己的切点和通知。这种原子化的设计让AOP配置变得非常灵活你可以编程式地组装多个Advisor把它们交给ProxyFactory来批量生成代理也可以插到BeanPostProcessor里对特定Bean动态增强。在aop:advisor配置中一个标签就对应一个PointcutAdvisor实例。通常的做法是先声明一个普通Bean它的类型就是某个Advice实现类比如MethodBeforeAdvice或AspectJMethodBeforeAdvice然后在aop:advisor标签里通过advice-ref指向这个Bean再通过expression指定切点表达式。这样配置出来的结果就是一个典型的DefaultPointcutAdvisor内部包含AspectJExpressionPointcut和对应的Advice。2.2 Advisor配置的完整过程和生效链路实际项目中直接用编程式Advisor的场景不算特别多但一旦用到就会非常优雅。比如想对某一组Bean做统一增强不希望通过aop:config大动干戈这时可以直接写一个BeanPostProcessor在postProcessAfterInitialization里面针对特定Bean创建Advisor并生成代理。来看一个典型的编程式Advisor配置流程Configuration public class AdvisorConfig { Bean public DefaultPointcutAdvisor myAdvisor() { // 声明一个Advisor实例 DefaultPointcutAdvisor advisor new DefaultPointcutAdvisor(); // 创建切点表达式匹配com.example包下所有类的所有方法 AspectJExpressionPointcut pointcut new AspectJExpressionPointcut(); pointcut.setExpression(execution(* com.example..*.*(..))); // 创建通知这里用一个简单的MethodInterceptor环绕通知 MethodInterceptor advice invocation - { try { System.out.println(开始时间记录 System.currentTimeMillis()); return invocation.proceed(); } finally { System.out.println(结束时间记录 System.currentTimeMillis()); } }; // 组装 advisor.setAdvice(advice); advisor.setPointcut(pointcut); return advisor; } }但光定义这个Bean还不够要让Spring在创建目标Bean时发现它并应用上去需要配合机制把这个Advisor塞进代理链路里。有两种常见做法一种是使用ProxyFactoryBean把interceptorNames指向该Advisor另一种是用强制自动代理Bean public BeanPostProcessor advisorAutoProxyCreator() { // 让所有Bean在初始化后被Scanner检查匹配的Bean会被自动代理 return new BeanPostProcessor() { Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof Advised) { return bean; } // 简易判断只代理Service后缀的类 if (beanName.endsWith(Service)) { ProxyFactory factory new ProxyFactory(bean); // myAdvisor从容器里取这里简化了获取逻辑 factory.addAdvisor(container.getBean(myAdvisor, PointcutAdvisor.class)); return factory.getProxy(); } return bean; } }; }这种方式的优势在于完全掌控注入点。你可以在某个Bean初始化完成后根据当前状态决定是否要增强它甚至可以根据Bean的实际属性动态决定用哪个Advisor这是注解和XML配置很难做到的精细控制。2.3 为什么说Advisor方式遇到的坑更少有一类常见问题用XML或注解配置时特别容易踩多个切面的执行顺序不可控。注解方式的顺序依赖于Bean名称的字符串排序和AspectJ注解的优先级规则如果不小心很容易排列出意外的顺序。而Advisor方式对顺序的控制非常直观。因为Advisor是一个个独立对象你可以把它们放进列表列表的顺序就是代理链执行的顺序。ProxyFactory在创建代理时会遍历Advisor数组前一个Advisor的执行顺序天然领先于后一个。这种“所见即所得”的顺序控制在需要严格编排切面逻辑的场合是好用的。不过也要说清楚Advisor方式的问题在于样板代码多。每增加一个切面就要写一个Advisor的构造代码远不如注解一行Aspect来得爽快。所以实际项目中我一般只在特殊场景下用Advisor比如需要对某些Bean做条件代理或者实现动态增强时才会选它。3. XML声明式配置AOP的原理标签背后的组装逻辑XML配置虽然在Spring Boot项目里不太常见了但理解它依然是理解整个Spring AOP机制的钥匙。尤其对于维护老项目的团队来说aop:config几乎是必读内容。3.1aop:aspect和aop:advisor到底有什么不同在XML配置里aop:config是根标签内部可以放aop:pointcut、aop:advisor和aop:aspect三个子标签。这里最容易混淆的就是后两个。aop:advisor是最贴近底层语义的配置它直接对应Spring的一个Advisor。它的两个关键属性advice-ref指向一个通知Bean这个Bean通常是MethodInterceptor或Advice的实现pointcut-ref或execution指定切点。Spring解析这个标签时会创建一个DefaultPointcutAdvisor把所有信息和关联的Advice包装在一起。aop:aspect则是更高层的抽象对应AspectJ里的切面概念通常由一个普通的JavaBean承载里面的方法配合增强标签来工作。它内部可以有aop:before、aop:after、aop:around等子标签这些标签会从JavaBean里定位对应方法把方法反射包装成Spring的Advice再和切点组合成一个Advisor。这一步非常关键一个aop:aspect里写了多个通知Spring会把它拆分成多个Advisor每个Advisor包含同一个Pointcut和其中一个通知方法。所以在运行时层面aop:aspect和aop:advisor的产物是一样的都是Advisor实例列表。区别只是写法上的“粒度”不同aop:advisor是直接组装一个标签对应一个Advisoraop:aspect是间接转换一个标签元素通过内部多个增强标签生成多个Advisor。3.2 XML配置的执行顺序与Spring的转换步骤Spring处理XML AOP配置的核心类是ConfigBeanDefinitionParser。这个类会逐个解析aop:config里的子元素把每个子标签解析成对应的BeanDefinition注册到容器中。解析过程中它内部还维护一个AopNamespaceUtils工具类负责注册自动代理创建器。默认情况下aop:config被解析时会向上下文注册一个AspectJAwareAdvisorAutoProxyCreator的BeanDefinition这就是一个BeanPostProcessor它的职责是在每个Bean初始化完成后检查所有装配好的Advisor看当前Bean是否匹配某个Advisor的Pointcut如果匹配就创建代理。那aop:aspect的JavaBean方法是怎么变成Advice的解析器在遇到aop:before标签时会生成一个AspectJMethodBeforeAdvice实例的BeanDefinition。这个Advice内部记录了目标Bean名称和对应的方法名称。在代理创建时Advice通过反射调用目标Bean的方法来执行切面逻辑。用一句话总结XML声明式配置的核心逻辑就是“把标签展开成BeanDefinition再让自动代理创建器把所有BeanDefinition转换好的Advisor应用到目标Bean上”。3.3 XML配置的完整示例与关键属性梳理直接上一份实际可用的XML配置注释里写了各自的作用。这份配置我常用在生产项目中兼顾了声明式和面向切面编程中比较复杂的场景。?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 !-- 1. 定义通知类这是一个普通Bean里面写具体逻辑 -- bean idlogAspect classcom.example.aop.LogAspect property nameprefix value【日志】/ /bean aop:config !-- 2. 声明公共切点表达式 -- aop:pointcut idservicePointcut expressionexecution(* com.example.service..*.*(..))/ !-- 3. 使用aspect方式直接把切面Bean的方法绑定到通知类型上 -- aop:aspect reflogAspect aop:before methodlogBefore pointcut-refservicePointcut/ aop:after-returning methodlogAfter returningresult pointcut-refservicePointcut/ aop:after-throwing methodlogException throwingex pointcut-refservicePointcut/ aop:around methodperformanceMonitor pointcut-refservicePointcut/ /aop:aspect /aop:config /beans对应的LogAspect类public class LogAspect { private String prefix; // getter/setter省略 public void logBefore(JoinPoint joinPoint) { System.out.println(prefix 前置通知方法 joinPoint.getSignature().getName()); } public void logAfter(Object result) { System.out.println(prefix 后置通知返回值 result); } public void logException(Throwable ex) { System.out.println(prefix 异常通知 ex.getMessage()); } public Object performanceMonitor(ProceedingJoinPoint joinPoint) throws Throwable { long start System.currentTimeMillis(); Object result joinPoint.proceed(); System.out.println(prefix 方法耗时 (System.currentTimeMillis() - start) ms); return result; } }需要特别注意的是aop:around的方法签名必须接收ProceedingJoinPoint参数并且调用proceed()来放行原始方法而aop:before等方法一般使用JoinPoint作为参数两者虽然名字像但是不能随便互换Spring对方法参数类型有严格校验参数类型不匹配容器启动时可能直接报错。3.4 为什么说XML配置适合大型传统项目XML配置的优势相当明确配置和代码完全分离不需要改Java代码就能调整切面逻辑。这在某些合规要求高、需要运维人员独立调整日志监控的场景里特别实用。另外XML配置具备“全局可见性”打开一个文件就能看到系统所有AOP切入点不用像注解那样各个类里翻来翻去。缺点同样明显维护成本高写起来啰嗦表达式写错了不容易在编译期发现只能在启动时排查。要是有选择的余地我更推荐注解方式配合类级别和表达式统一管理但如果你在维护老项目这份XML示例可以直接用同时心里要有数XML配置和注解配置的未来趋势都是收拢到AspectJ语法体系表达式语法可以互相移植。4. 底层代理机制JDK动态代理与CGLIB动态代理的选择逻辑了解配置层面的原理之后必须往下钻一层看看Spring究竟怎么把Advisor应用上去、生成的目标对象是个什么东西。这就是整个AOP运行原理最核心的一环动态代理机制。4.1 JDK动态代理基于接口的轻量方案JDK动态代理是Java平台自带的代理机制位于java.lang.reflect包下核心类有Proxy和InvocationHandler。它的运行机制是这样JVM在运行时为目标接口动态生成一个代理类这个代理类和目标类实现了相同的接口当外部调用这个接口方法时实际执行的是InvocationHandler.invoke()方法里的逻辑。在invoke()方法内部Spring会根据当前方法名去匹配所有已注册的Pointcut如果匹配成功就按顺序执行对应的Advice逻辑然后在适当的时机调用method.invoke(target, args)放行到真实目标对象。如果不匹配直接反射调用目标方法不做额外增强。JDK动态代理的优点很明显不依赖任何第三方库反射开销相对小生成的类也少。但它的局限也很致命只能代理接口。如果目标类没有实现任何接口或者说调用方拿到的Bean不是通过接口引用的JDK动态代理根本无法生成代理对象。举一个典型场景项目里定义了UserService接口和UserServiceImpl实现类注入时用的是UserService接口类型那么Spring可以选用JDK动态代理。但如果某天代码里直接Autowired UserServiceImpl并且Spring选择了JDK代理方式就会报BeanNotOfRequiredTypeException因为注入的实例实际上是一个Proxy对象它实现的是UserService而不是UserServiceImpl类型上不匹配。4.2 CGLIB动态代理基于继承的强力方案CGLIBCode Generation Library是一种比JDK动态代理更“底层”的代理机制它通过生成目标类的子类来代理目标对象。子类会重写父类的非final方法在重写逻辑里插入Advice的执行代码。CGLIB代理的实现类在Spring 4之后整合进了org.springframework.cglib包中核心类是Enhancer和MethodInterceptor。Enhancer负责设置父类即目标类、设置回调逻辑即MethodInterceptor然后生成子类字节码。每次调用代理方法都会先经过MethodInterceptor.intercept()方法在这个方法里做切点匹配和Advice链调用。CGLIB的一个显著特点是不需要目标类实现任何接口继承机制决定了它能代理任何普通类。但是两个注意点一目标类或方法不能是final修饰的因为是继承生成子类再重写方法final方法无法重写此时代理会退化成直连目标类调用切面逻辑会跳过二CGLIB创建代理对象的成本相对JDK动态代理要高一些因为要生成字节码。4.3 Spring的代理选择逻辑与proxy-target-classSpring判断用哪种代理方式的逻辑其实很简单集中在DefaultAopProxyFactory里如果proxy-target-class为true直接用CGLIB如果为false默认再判断目标类是否实现了接口实现了就选JDK没实现就退回CGLIB。Spring Boot 2.x以后默认行为改了Spring Boot应用默认使用CGLIB代理即使目标类实现了接口也会强制proxy-target-classtrue。这个变化的官方理由主要是为了避免类型转换问题的困扰同时CGLIB经过多年优化后性能差距已经很小了。但在老项目的Spring XML环境里默认仍是JDK优先所以如果你维护老项目特别容易遇到“接口和实现类混用导致注入失败”的问题。给个判断表方便快速定位场景目标类情况默认代理方式Spring Boot 2.x默认代理方式老XML项目隐患实现接口注入时用接口类型CGLIBJDK无实现接口注入时用实现类类型CGLIBJDK老项目可能BeanNotOfRequiredTypeException没实现接口的普通类CGLIBCGLIB目标类final会有问题抽象类CGLIB不可用CGLIB不可用无法被代理需注意4.4 代理机制对“切面不生效”类问题的影响搞清楚了代理选择逻辑很多诡异问题就有了解答思路。比如方法内部自调用不走代理的问题这和动态代理机制直接相关。自调用指的是this.methodB()这种调用它绕过的是代理对象直接在目标类内部执行了原方法。因为代理对象只能在外部调用时介入内部this调用根本没有经过Proxy引用。这个现象在JDK和CGLIB代理下都存在除非把目标类自己注入自己显式通过代理对象调用。另外getClass()在代理对象上返回的也是代理类而不是原始类。如果代码里有bean.getClass().getAnnotation()这类操作在代理对象上很可能拿不到目标类上的注解这也是动态代理机制带来的坑。遇到这种情况可以通过AopUtils.getTargetClass()获取原始目标类。5. 实操演练从零完成一个带日志与事务的AOP配置前面把原理讲得比较多现在落到实操层面从零完整走一遍。这里目标场景是给com.example.service包下所有Service方法打印日志再包裹一个简单的性能监控同时在指定方法上挂载事务拦截。为了体现两种配置方式的差异我会写上对应实现。5.1 使用XML方式实现的完整配置XML配置适合那种不想动Java代码、希望运维随时改切面的场景。先是spring-context.xml里配置AOP相关aop:config !-- 全包方法切入除开某些特殊方法 -- aop:pointcut idserviceMethods expressionexecution(public * com.example.service..*.*(..))/ !-- 日志切面 -- aop:aspect reflogAspect aop:around methodlogAround pointcut-refserviceMethods/ /aop:aspect !-- 事务切面这里直接引用已有的transactionManager -- aop:advisor advice-reftransactionInterceptor pointcut-refserviceMethods order1/ /aop:config !-- Spring自带的事务拦截器利用TransactionInterceptor把事务管理器和切面绑定 -- bean idtransactionInterceptor classorg.springframework.transaction.interceptor.TransactionInterceptor property nametransactionManager reftransactionManager/ property nametransactionAttributes props prop keyget*PROPAGATION_SUPPORTS,readOnly/prop prop keysave*PROPAGATION_REQUIRED/prop prop keyupdate*PROPAGATION_REQUIRED/prop prop keydelete*PROPAGATION_REQUIRED/prop /props /property /bean这份配置里用到了aop:advisor并且把日志切面和事务拦截器同时配置在同一个aop:config下。这两个Registry会被Spring顺序执行order1的优先级更高事务外层包裹日志内层执行细节。这里执行的细节很讲究假如事务通知先于日志通知运行整个方法入口先进事务然后进入日志监控方法日志包裹住了业务逻辑如果顺序反了日志先运行事务再运行其实出入不大但如果你在日志里拿到了事务提交/回滚的状态顺序就很重要了。5.2 使用Advisor编程方式实现的等价方案XML虽直观但你要是想用代码完全控制同样的配置就可以把上面这套转换成编程式Advisor。这种写法适合在公共模块里自己构建一套统一配置然后把不同包的不同切入点通过条件装配组合起来。Configuration public class AspectConfiguration { Bean public TransactionInterceptor transactionInterceptor(PlatformTransactionManager transactionManager) { TransactionInterceptor interceptor new TransactionInterceptor(); interceptor.setTransactionManager(transactionManager); Properties props new Properties(); props.setProperty(get*, PROPAGATION_SUPPORTS,readOnly); props.setProperty(save*, PROPAGATION_REQUIRED); props.setProperty(update*, PROPAGATION_REQUIRED); props.setProperty(delete*, PROPAGATION_REQUIRED); interceptor.setTransactionAttributes(props); return interceptor; } Bean public AdapterAdvisor logAdvisor() { AspectJExpressionPointcut pointcut new AspectJExpressionPointcut(); pointcut.setExpression(execution(public * com.example.service..*.*(..))); MethodInterceptor logAdvice invocation - { long start System.currentTimeMillis(); try { return invocation.proceed(); } finally { System.out.println(方法耗时 (System.currentTimeMillis() - start) ms); } }; DefaultPointcutAdvisor advisor new DefaultPointcutAdvisor(); advisor.setAdvice(logAdvice); advisor.setPointcut(pointcut); advisor.setOrder(1); return advisor; } Bean public AdapterAdvisor transactionAdvisor(TransactionInterceptor transactionInterceptor) { AspectJExpressionPointcut pointcut new AspectJExpressionPointcut(); pointcut.setExpression(execution(public * com.example.service..*.*(..))); DefaultPointcutAdvisor advisor new DefaultPointcutAdvisor(); advisor.setAdvice(transactionInterceptor); advisor.setPointcut(pointcut); advisor.setOrder(2); return advisor; } }这段代码在业务上是基于DefaultPointcutAdvisor组装出两个Advisor一个是日志的一个是事务的顺序控制通过setOrder实现。这个配置写完后需要有自动代理创建器或者BeanFactoryTransactionAttributeSourceAdvisor这样的扩展点把Advisor应用到具体Bean上。5.3 运行验证切面生效后看到什么配置完之后启动应用只要切点能正确匹配调用UserService.saveUser()方法时输出大致如下方法其实还没进来事务先开启了 【性能】开始时间2025-01-15T15:30:10.123 实际执行业务代码... 【性能】结束时间2025-01-15T15:30:10.456耗时333ms 事务提交了顺序的具体表现取决于order值。如果日志Advisor优先会先打印“开始”然后进入事务拦截器事务开启后执行业务方法再逐层返回打印耗时最后提交事务。用这套观察顺序就能验证自己配置的通知顺序是否正确没有输出顺序也说明切点没匹配上或者代理未生成。6. 常见问题与排查技巧AOP配置不生效时怎么办这部分是干货里的干货。我把自己踩过的坑按“症状、原因、解法”整理成速查表遇到问题时对着查即可。6.1 高频问题对照表现象可能原因排查方法解决方案切面完全不执行切点表达式写得不匹配包名或方法名用AopUtils或日志排查实际调用的类和方法签名调整表达式建议先写execution(* com.example..*.*(..))再收窄启动报BeanNotOfRequiredTypeException注入用具体实现类但代理类型是JDK看日志里注入的bean实例class注入接口或强制proxy-target-classtrue自调用时切面不执行方法内部用this调用另一个方法代理对象被绕过打断点看调用栈是否是ReflectiveMethodInvocation注入代理对象引用自身或者把内部调用拆到另一个Bean切面时灵时不灵有些方法为final检查目标类的final方法定义去掉final或改用接口设计多个切面顺序不对没有设置order值按默认排序在日志前后打印标记观察顺序给Advisor或Order显式设定顺序Aspect类里的方法包装异常方法参数类型和对应通知标签不匹配检查方法是否使用ProceedingJoinPoint却配了aop:before按通知类型匹配参数类型XML配置解析报错缺少aop命名空间或schema检查xmlns:aop和xsi:schemaLocation补全命名空间6.2 定位切面不生效的三个排查步骤第一步看Spring版本和代理方式。用AopUtils.isAopProxy(bean)可以快速判断当前Bean是不是代理对象返回false说明代理环节本身没介入。这不是在代码里硬写而是说你可以临时在代码输出或者debug表达式里调用这个工具方法来确认Spring到底有没有创建代理。第二步看切点表达式语法。AspectJ切点表达式很容易因为“一个空格”翻车例如execution (public *)里的空格、通配符数量和包名大小写。建议先在Aspect里写一个临时切点表达式来做最小化验证确认能匹配后再套用到XML里逐步扩大范围。第三步看容器中是否同时注册了多个自动代理创建器。有些项目同时引入了Spring AOP和AspectJ相关依赖可能造成多个AbstractAutoProxyCreator实例抢占代理。如果出现这种情况容器里会注册两个都叫“internalAutoProxyCreator”之类的Bean处理器容易导致代理规则混乱。检查方法是排除重复依赖尤其注意老项目同时引入spring-boot-starter-aop和旧Spring版本依赖的情况。6.3 关于代理对象的最强排查技巧最终极的排查方式就是看Json把你注入的Bean打出来看class属性里的内容。JDK代理的类名通常是$Proxy123这种带$的CGLIB代理的类名通常是$$EnhancerBySpringCGLIB$$这种明显变长的。看到这些形态就能确认代理生效了。如果你的Bean的class是UserServiceImpl本身那就说明完全没进代理。我在排查时的经验是把这些信息统一输出到日志里比其他任何方式都快System.out.println(bean.getClass().getName()); // 输出样式示例 // com.example.service.UserServiceImpl // com.example.service.UserServiceImpl$$EnhancerBySpringCGLIB$$1234abcd // com.sun.proxy.$Proxy12Class名称直接说明了问题所在的代理流派也能帮助你判断是不是某个切面类贴错位置导致的代理遗漏。这个方法没有副作用生产上临时打印也行排查完删掉即可。7. 个人经验总结选择建议与配置心得把所有原理和坑讲完之后结合我自己的项目经验聊聊什么时候用什么方式。这些不是教科书答案是比较务实的参考。对于新项目我强烈推荐注解方式AspectAround等配合Spring Boot默认的CGLIB代理简单直接代码可读性好。这时候没必要去碰Advisor接口除非你在写框架级组件或公共starter。对于老项目或者对切面配置有“不修改代码就能调整”诉求的项目比如审计日志切换规则由非开发人员维护XML方式和aop:advisor是更好的载体。它把规则外置化所见即所得但注意给关键aop:advisor设置order值防止切面顺序失控。对于框架开发场景比如自己封装分布式锁、多租户数据隔离、权限缓存处理等Advisor编程式配置往往最顺手。因为框架源码里经常需要根据注解或配置动态组装切面直接在代码里new一个DefaultPointcutAdvisor比解析XML配置要可控得多。最后分享一个我自己的习惯无论用哪种方式都要在项目里维护一份“切面清单”记录所有切点表达式、通知顺序和试点效果。这个习惯帮我解决过很多次“看起来切面很多实际作用含糊”的问题。AOP配置本质上是给代码织了一张隐形的逻辑网你最好在网络外面留一张地图而不是等到业务事故时才去翻XML。这篇文章把advisor和XML两种配置方式从原理到实操都过了一遍底层动态代理机制也单独展开讲了。希望你能用自己的工程实践来佐证这些结论少踩几个我当年踩过的坑。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询