Skip to content

Spring AOP 使用指南:前世与今生 ​

一句话定位:把 Spring AOP 的三种配置形态(1.2 接口式 → XML schema → @AspectJ 注解)与 Advice/Advisor/Interceptor 等术语一次讲透,AOP 使用层的面试完备地图;代理是怎么造出来的见姊妹篇 [Spring AOP源码解析](Spring AOP源码解析.md)。

1 先分清三个名词:AOP、AspectJ、Spring AOP ​

AOP 的本质:在方法执行前、返回后、抛异常后等位置做拦截增强。Java 语言本身没提供这种钩子,实现手段是生成代理类——对外提供服务的实际是代理实例。

  • Spring AOP:基于动态代理;目标类有接口默认 JDK 动态代理,无接口用 CGLIB(Spring 3.2 起 spring-core 内置 CGLIB 与 ASM,无需额外依赖);依赖 IOC 容器管理,只能作用于容器中 Bean 的方法;Web 场景有时 Filter/Interceptor 比 AOP 更合适;性能弱于 AspectJ——启动时要生成代理,方法调用增加栈深度;
  • AspectJ:Eclipse 基金会出品的 AOP 完整方案,靠修改字节码(静态织入)实现,织入发生在运行之前,因此无额外运行时开销;三种织入时机:compile-time(编译期)、post-compile(编译后,增强已编译类)、load-time(类加载期);
  • Spring AOP 只解决企业开发中最普遍的"方法织入"需求,是 AspectJ 能力的一个子集——完整用法见 AspectJ使用指南。

2 Spring 1.2 接口式配置:Advice、Advisor、Interceptor ​

最古老的用法,也是读 Spring AOP 源码的起点。为每个 bean 配一个 ProxyFactoryBean:

xml
<bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target" ref="userService"/>
    <property name="interceptorNames">
        <list>
            <value>logArgsAdvisor</value>
        </list>
    </property>
</bean>

两个致命局限:要拦截 OrderService、PostService 就得再各配一个代理;拦截粒度只到类级别,类中所有方法都被拦。

三个关键概念(碰到一个记一个):

  • Advice(通知):拦截到之后"做什么",如 MethodBeforeAdvice 打印入参、AfterReturningAdvice 打印返回值;
  • Advisor(切面holder):决定"拦哪些方法",内部持有一个 Advice。NameMatchMethodPointcutAdvisor 按 mappedNames(逗号分隔多个方法名)匹配;RegexpMethodPointcutAdvisor 用正则 pattern,如 com.javadoop.*.service.*.create.*,方法级粒度;
  • Interceptor(拦截器):实现 MethodInterceptor,在 invoke(MethodInvocation) 里 invocation.proceed() 前后包夹逻辑,是"能做最完整事情"的形态(Spring 内部统一把 Advice 适配成 Interceptor 执行)。
java
public class DebugInterceptor implements MethodInterceptor {
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("before: " + invocation.getMethod().getName());
        Object result = invocation.proceed();
        System.out.println("after: " + invocation.getMethod().getName());
        return result;
    }
}

3 自动代理:从逐个 ProxyFactoryBean 到 AutoProxyCreator ​

autoproxy 解决"每个 bean 配一个代理"的问题,使用者直接按原类型 getBean,拿到的已是代理:

  • BeanNameAutoProxyCreator:beanNames 支持模式匹配(如 *ServiceImpl),interceptorNames 可挂 Advice/Interceptor/Advisor——它自己决定拦哪些 bean,方法粒度交给内部 advice;
  • DefaultAdvisorAutoProxyCreator:配置只要一行,让容器中所有 Advisor 自动生效——由各 Advisor 的 Pointcut 自己去匹配 bean 的方法,匹配上才执行其内部 advice。
xml
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
    <property name="beanNames" value="*ServiceImpl"/>
    <property name="interceptorNames" value="logArgsAdvice"/>
</bean>

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

两者关系是"粗粒度按名圈 bean"与"细粒度让 Advisor 各显神通"的差别;DefaultAdvisorAutoProxyCreator 本身是个 BeanPostProcessor,这正是源码篇的入口。

4 @AspectJ 注解方式:当下绝对主流 ​

名字里带 AspectJ,但和 AspectJ 织入器没有关系——只借用它的注解与切点表达式语法,实现是纯 Spring AOP。依赖 aspectjweaver(Boot 用 spring-boot-starter-aop);开启方式二选一:XML 的 <aop:aspectj-autoproxy/> 或 Java 配置 @Configuration + @EnableAspectJAutoProxy。@Aspect 必须作用在一个 bean 上(@Component 或 XML 定义均可)。

切点表达式,常用匹配原语:

  • execution(* transfer(..)):方法签名匹配,最常用;.. 在参数位表示任意参数;
  • within(com.javadoop.springaoplearning.service..*):按类/包圈范围(. 一个包名,.. 包及子包);
  • @annotation(...):按方法上的注解匹配,常与 && 组合使用;
  • bean(*Service):按 bean 名匹配,Spring 扩展。

官方推荐的 SystemArchitecture 模式:在一个类里集中定义各层切点(web 层、service 层、dao 层)供各切面复用,避免表达式散落。

五种通知:

java
@Aspect
@Component
public class LogAspect {
    @Pointcut("execution(* com.javadoop.springaoplearning.service.*.*(..))")
    public void businessService() {}

    @Before("businessService()")
    public void logArgs(JoinPoint joinPoint) {          // 第一个参数必须是 JoinPoint
        System.out.println(Arrays.toString(joinPoint.getArgs()));
    }

    @AfterReturning(value = "businessService()", returning = "retVal")
    public void logResult(Object retVal) {}             // 可绑定返回值

    @AfterThrowing(value = "businessService()", throwing = "ex")
    public void logException(DataAccessException ex) {} // 可绑定异常类型

    @After("businessService()")
    public void finallyLog() {}                         // finally 语义,正常异常都走

    @Around("businessService()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object result = pjp.proceed();                  // 执行原方法,也可 pjp.proceed(args) 换参
        return result;
    }
}
  • @Around 最全能,"既做得到 @Before 的事,也做得到 @AfterReturning 的事",还能改参数、改返回值、吞异常;
  • 取入参用 JoinPoint#getArgs();ProceedingJoinPoint 是其子接口,多了 proceed()/proceed(Object[]),只有 @Around 能用;
  • 两个不生效场景:切面配置不会作用于 @Aspect bean 自身;目标对象内部 this 自调用不经过代理,同样不触发增强。

5 XML schema 方式:aop:config ​

解析入口是 org.springframework.aop.config.AopNamespaceHandler。结构上所有配置收在 <aop:config> 下,<aop:aspect ref="aBean"> 指定承载处理逻辑的普通 bean:

xml
<aop:config>
    <aop:pointcut id="businessService"
        expression="execution(* com.javadoop.springaoplearning.service.*.*(..))"/>
    <aop:aspect ref="logArgsAspect">
        <aop:before pointcut-ref="businessService" method="logArgs"/>
    </aop:aspect>
    <aop:aspect ref="logResultAspect">
        <aop:after-returning pointcut-ref="businessService" method="logResult"/>
    </aop:aspect>
</aop:config>
  • pointcut 作为 <aop:config> 直接子元素是全局的;放在 <aop:aspect> 内则仅该切面可用;
  • expression 也可以引用注解方式定义的切点(如 com.javadoop.SystemArchitecture.businessService()),两套体系可混用;
  • 各通知元素与第 4 节注解一一对应,适合无源码、需集中管控配置的场景。

实操提示:验证 AOP 配置优先用命令行(mvn 打包后 java -jar)而非 IDE——IDE 的构建与运行代理常把问题"智能"掉。

面试追问链 ​

  • Spring AOP 和 AspectJ 什么关系?→ 只借注解与切点表达式,实现是纯 Spring 动态代理;AspectJ 才是靠字节码织入的完整方案;
  • 有接口和无接口分别用什么代理?→ JDK 动态代理 / CGLIB;
  • Advice 和 Advisor 谁决定拦谁?→ Advice 管"做什么",Advisor = Pointcut + Advice 管"拦哪些";
  • @After 和 @AfterReturning 区别?→ finally 语义(正常异常都执行)vs 仅正常返回后执行;
  • 同类自调用为什么切面不生效?→ this 调用不经过代理对象;解法是自注入或 expose-proxy 拿 AopContext.currentProxy();
  • 三种配置方式怎么选?→ 注解为主流;XML schema 用于集中配置/无源码场景;接口式只配读源码和面试;
  • DefaultAdvisorAutoProxyCreator 厉害在哪?→ 一行配置让所有 Advisor 自动生效,本质是 BeanPostProcessor,在 Bean 初始化后包代理。

面试官视角小结 ​

使用层考 AOP,面试官想确认三件事:知不知道代理才是本质(顺带引出 JDK/CGLIB 的选择规则);能不能写对 execution 表达式、分清五种通知语义(尤其 @After vs @AfterReturning);有没有踩过自调用不生效、切面作用范围这类坑。答完主动补一句"Advisor 的匹配与代理创建发生在 BeanPostProcessor 阶段",就把话题引向自己熟悉的主场——链路详见 [Spring AOP源码解析](Spring AOP源码解析.md);若被问"Spring AOP 拦不了构造器/字段怎么办",接 AspectJ 静态织入即可,见 AspectJ使用指南。

来源:javadoop《Spring AOP 使用介绍,从前世到今生》(https://javadoop.com/post/spring-aop-intro)——本文为研读消化后的重写笔记,原文版权归 javadoop,引用请遵原文。