【大白话说Java面试题 第150题】【06_Spring篇】第10题:解释一下 Spring AOP 里面的几个名词

📌 PDF :大白话说Java面试题 --- 06_Spring篇

第10题:解释一下 Spring AOP 里面的几个名词

📚 回答:

  • 核心考点 : Spring AOP 的名词体系是理解 AOP 的基石,大厂面试不会只问"有哪几个名词",而是深入考察 每个名词的源码级实现 (如 AspectJExpressionPointcut 如何解析表达式)、五种通知类型的执行顺序与异常传播织入的三种时机与 Spring 的选择原因以及 JoinPointProceedingJoinPoint 的区别。面试官真正想判断的是:你是否建立了从概念到源码的完整认知链路,能否在工程实践中准确运用这些概念设计和排查问题。

1. 切面(Aspect)
  • 1.1 定义与本质

    切面是横切关注点的模块化封装 ,包含一组**通知(Advice) 切入点(Pointcut)**的集合。在 Spring 中,切面通过 @Aspect 注解标识,由 Spring 容器管理。

    java 复制代码
    @Aspect
    @Component
    public class LoggingAspect {
        // 切入点定义
        @Pointcut("execution(* com.example.service.*.*(..))")
        public void serviceMethods() {}
    
        // 通知定义
        @Before("serviceMethods()")
        public void logBefore(JoinPoint joinPoint) {
            System.out.println("[日志切面] 方法开始: " + joinPoint.getSignature().getName());
        }
    }
  • 1.2 切面的生命周期

    阶段 说明 对应源码
    解析 Spring 扫描 @Aspect 注解,解析切点表达式 AspectJAutoProxyCreator
    注册 将切面 Bean 注册到 BeanFactory BeanDefinitionRegistry
    代理创建 根据切点匹配结果,为目标 Bean 创建代理 AbstractAutoProxyCreator
    织入 将通知织入到代理对象的方法调用链 JdkDynamicAopProxy / CglibAopProxy
  • 1.3 多切面优先级控制

    当多个切面同时匹配同一连接点时,通过 @Order 注解或实现 Ordered 接口控制执行顺序:

    java 复制代码
    @Aspect
    @Order(1)  // 数值越小,优先级越高,越先执行
    public class SecurityAspect { ... }
    
    @Aspect
    @Order(2)
    public class LogAspect { ... }

    执行顺序规则 :优先级高的切面的 @Before 先执行,但 @After 后执行(类似栈结构)。


2. 连接点(Join Point)
  • 2.1 定义与范围

    连接点是程序执行过程中的某个特定点 ,是 AOP 可以插入逻辑的位置。在 Spring AOP 中,连接点仅限于方法调用(因为 Spring AOP 基于代理实现)。

    AOP 框架 支持的连接点类型
    Spring AOP 仅方法调用
    AspectJ 方法调用、构造器调用、字段访问、异常处理等
  • 2.2 JoinPoint 对象详解

    JoinPoint 是 Spring AOP 提供的运行时对象,封装了当前连接点的全部信息:

    java 复制代码
    @Before("serviceMethods()")
    public void logBefore(JoinPoint joinPoint) {
        // 获取目标对象
        Object target = joinPoint.getTarget();
        // 获取代理对象
        Object proxy = joinPoint.getThis();
        // 获取方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        // 获取方法名
        String methodName = signature.getName();
        // 获取参数
        Object[] args = joinPoint.getArgs();
        // 获取参数类型
        Class<?>[] paramTypes = signature.getParameterTypes();
    }
    JoinPoint API 说明
    getTarget() 获取被代理的目标对象(原始对象)
    getThis() 获取代理对象本身
    getSignature() 获取连接点的签名(方法签名)
    getArgs() 获取方法参数数组
    getKind() 获取连接点类型(如 method-execution
    toLongString() 获取完整的方法描述
  • 2.3 ProceedingJoinPoint------环绕通知专用

    ProceedingJoinPointJoinPoint 的子接口,仅用于 @Around 通知 ,增加了 proceed() 方法控制目标方法的执行:

    java 复制代码
    @Around("serviceMethods()")
    public Object logAround(ProceedingJoinPoint pjp) throws Throwable {
        // 前置逻辑
        long start = System.currentTimeMillis();
    
        // 执行目标方法(关键!不调用则目标方法不会执行)
        Object result = pjp.proceed();
        // 也可以修改参数:pjp.proceed(new Object[]{newArg1, newArg2});
    
        // 后置逻辑
        long cost = System.currentTimeMillis() - start;
        System.out.println("方法耗时: " + cost + "ms");
    
        // 可以修改返回值
        return result;
    }

    ⚠️ 重要@Around 通知中必须调用 proceed(),否则目标方法不会执行。这是最常见的环绕通知使用错误。


3. 切入点(Pointcut)
  • 3.1 定义与本质

    切入点是匹配连接点的表达式,定义了"在哪里"织入通知。Spring AOP 使用 AspectJ 的切点表达式语法。

  • 3.2 切点表达式语法详解

    execution(修饰符 返回类型 包名.类名.方法名(参数) 异常)

    组成部分 说明 示例
    修饰符 可选,public/private/protected public
    返回类型 必填,* 表示任意 *voidString
    包名 必填,. 表示当前包,.. 表示任意子包 com.servicecom..service
    类名 必填,* 表示任意 UserService*Service
    方法名 必填,* 表示任意 addUser*
    参数 必填,() 无参、(..) 任意、(*, String) 指定 (Long)(String, ..)
    异常 可选 throws IOException

    常用表达式示例

    java 复制代码
    @Pointcut("execution(public * com.example.service.*.*(..))")           // service包下所有public方法
    @Pointcut("execution(* com.example.service..*.*(..))")                // service包及其子包下所有方法
    @Pointcut("execution(* com.example.service.UserService.add*(..))")     // UserService中以add开头的方法
    @Pointcut("execution(* com.example.service.*Service.*(..))")          // 以Service结尾的类中的所有方法
    @Pointcut("execution(* com.example.service.*.*(Long, ..))")           // 第一个参数为Long的方法
    @Pointcut("execution(* com.example.service.*.*(..)) throws Exception") // 声明抛出Exception的方法
  • 3.3 其他切点指示器

    指示器 说明 示例
    @annotation 匹配带有指定注解的方法 @annotation(com.example.Log)
    within 匹配指定类型内的方法 within(com.example.service.*)
    this 匹配代理对象为指定类型的 this(com.example.service.UserService)
    target 匹配目标对象为指定类型的 target(com.example.service.UserService)
    args 匹配参数为指定类型的 args(java.lang.String)
    bean 匹配指定 Bean 名称 bean(userService)
    @within 匹配类上有指定注解的 @within(com.example.Service)
    @target 匹配目标对象类上有指定注解的 @target(com.example.Service)
    @args 匹配参数类上有指定注解的 @args(com.example.Valid)

    组合表达式

    java 复制代码
    @Pointcut("execution(* com.example.service.*.*(..)) && @annotation(com.example.Log)")
    public void loggedServiceMethods() {}  // 匹配service包下且带有@Log注解的方法
    
    @Pointcut("execution(* com.example.service.*.*(..)) || execution(* com.example.controller.*.*(..))")
    public void serviceOrController() {}  // 匹配service或controller包下的方法
  • 3.4 切点解析的底层实现

    Spring 使用 AspectJExpressionPointcut 解析切点表达式,底层依赖 AspectJ 的 PointcutParser

    java 复制代码
    // Spring 源码简化示意
    public class AspectJExpressionPointcut implements Pointcut {
        private String expression;  // 切点表达式字符串
    
        @Override
        public boolean matches(Method method, Class<?> targetClass) {
            // 使用 AspectJ 的 PointcutExpression 进行匹配
            PointcutExpression pcExpression = 
                pointcutParser.parsePointcutExpression(expression);
            return pcExpression.matchesMethodExecution(method).alwaysMatches();
        }
    }

4. 通知(Advice)
  • 4.1 五种通知类型深度解析

    通知类型 注解 执行时机 特点 典型场景
    前置通知 @Before 目标方法执行前 无法阻止方法执行 权限校验、参数校验
    后置通知 @After 目标方法执行后(无论是否异常) 类似 finally 资源释放、清理
    返回通知 @AfterReturning 目标方法正常返回后 可获取返回值 日志记录、缓存更新
    异常通知 @AfterThrowing 目标方法抛出异常后 可获取异常对象 异常告警、补偿逻辑
    环绕通知 @Around 目标方法执行前后 完全控制执行流程 事务管理、性能监控
  • 4.2 通知执行顺序详解

    单一切面内的执行顺序

    复制代码
    @Around 前半部分(proceed() 之前)
        ↓
    @Before
        ↓
    【目标方法执行】
        ↓
    @AfterReturning(正常返回)或 @AfterThrowing(抛出异常)
        ↓
    @After(无论是否异常,最终执行)
        ↓
    @Around 后半部分(proceed() 之后)

    异常场景下的执行顺序

    复制代码
    @Around 前半部分
        ↓
    @Before
        ↓
    【目标方法执行 → 抛出异常】
        ↓
    @AfterThrowing(捕获异常)
        ↓
    @After(仍然执行!)
        ↓
    @Around 后半部分(异常会传播到此处,需处理)

    ⚠️ 注意@After 类似于 finally,无论是否异常都会执行。@Around 中如果 proceed() 抛出异常,异常会传播到 @Around 的后半部分。

  • 4.3 通知的参数绑定

    Spring AOP 支持将连接点信息绑定到通知方法的参数:

    java 复制代码
    // 绑定返回值
    @AfterReturning(pointcut = "serviceMethods()", returning = "result")
    public void logAfterReturning(Object result) {
        System.out.println("方法返回: " + result);
    }
    
    // 绑定异常
    @AfterThrowing(pointcut = "serviceMethods()", throwing = "ex")
    public void logAfterThrowing(Exception ex) {
        System.out.println("方法异常: " + ex.getMessage());
    }
    
    // 绑定注解
    @Before("@annotation(log)")
    public void logWithAnnotation(Log log) {
        System.out.println("注解值: " + log.value());
    }
    
    // 绑定参数
    @Before("execution(* com.example.service.*.*(Long,..)) && args(id,..)")
    public void logWithParam(Long id) {
        System.out.println("参数ID: " + id);
    }
  • 4.4 通知的异常传播

    通知类型 异常处理行为
    @Before 抛出异常会阻止目标方法执行
    @AfterReturning 抛出异常会覆盖目标方法的正常返回,最终向外抛出
    @AfterThrowing 抛出异常会覆盖原始异常,最终向外抛出
    @After 抛出异常会覆盖原始异常(如果存在),最终向外抛出
    @Around 完全控制异常,可捕获、可抛出、可转换

5. 织入(Weaving)
  • 5.1 定义与三种织入时机

    织入是将切面应用到目标对象的过程,即把通知逻辑插入到连接点的过程。AOP 规范定义了三种织入时机:

    织入时机 英文 实现方式 特点 代表框架
    编译期织入 Compile-time weaving 特殊的编译器在编译时修改源码 无运行时开销,需特殊编译器 AspectJ AJC
    加载期织入 Load-time weaving 类加载器在加载类时修改字节码 无运行时开销,需特殊类加载器 AspectJ LTW
    运行期织入 Runtime weaving 运行时动态生成代理对象 有代理开销,使用简单 Spring AOP
  • 5.2 Spring AOP 为什么选择运行期织入?

    Spring AOP 选择运行期织入(动态代理)的原因:

    1. 无侵入性:无需修改编译器或类加载器,纯 Java 实现;
    2. 与 IoC 集成:利用 Spring 容器管理 Bean 的生命周期,在 Bean 初始化后创建代理;
    3. 使用简单:注解驱动,零配置即可使用;
    4. 满足大多数场景:企业应用中 95% 的 AOP 需求是方法拦截,运行期织入足够。

    代价:每次方法调用都经过代理,有一定性能开销(但通常可忽略)。

  • 5.3 Spring AOP 织入的源码流程

    复制代码
    Bean 实例化
        ↓
    Bean 属性填充(依赖注入)
        ↓
    Bean 初始化(@PostConstruct、InitializingBean)
        ↓
    【关键】AbstractAutoProxyCreator.postProcessAfterInitialization()
        ↓
    检查 Bean 是否匹配任何切点
        ↓
    是 → 创建代理对象(JDK 或 CGLIB)替换原始 Bean
        ↓
    否 → 返回原始 Bean
        ↓
    代理对象注入到依赖方

6. 目标对象(Target)与代理(Proxy)
  • 6.1 目标对象(Target)

    被代理的原始对象,即业务逻辑的真正执行者。在 Spring AOP 中,目标对象通常是 Spring 容器管理的 Bean。

  • 6.2 代理(Proxy)

    AOP 框架生成的代理对象,客户端实际持有的引用。代理对象拦截方法调用,在适当位置执行通知逻辑。

    代理类型 实现方式 适用场景
    JDK 动态代理 实现目标接口 目标类实现了接口
    CGLIB 代理 继承目标类 目标类未实现接口,或强制使用
  • 6.3 目标对象 vs 代理对象的区别

    java 复制代码
    @Autowired
    private UserService userService;  // 注入的是代理对象!
    
    public void test() {
        // userService 是代理对象(JDK 代理时类型为 $ProxyXX,CGLIB 时为 UserService$$EnhancerBySpringCGLIB)
        userService.addUser();  // 调用经过代理,触发切面
    
        // 获取目标对象
        if (AopUtils.isAopProxy(userService)) {
            UserService target = (UserService) AopProxyUtils.ultimateTargetClass(userService);
        }
    }

7. 引入(Introduction)

引入是 Spring AOP 的一个高级特性,允许为目标对象动态添加新的接口实现

java 复制代码
@DeclareParents(value = "com.example.service.*", defaultImpl = DefaultUsageTracked.class)
private UsageTracked mixin;  // 为所有 Service 类动态添加 UsageTracked 接口实现
特性 说明
作用 动态为目标类添加新接口,无需修改源码
实现 通过代理对象实现多个接口
场景 为现有类添加监控、统计等功能

8. 面试官追问与高分回答模板
  • 追问 1:"Spring AOP 中有哪些关键名词?分别是什么意思?"

    低分回答:"有切面、连接点、切入点、通知、织入。切面是模块化的横切关注点,连接点是程序执行的特定点,切入点是匹配连接点的表达式,通知是切面执行的动作,织入是将切面应用到目标对象的过程。"(背诵式回答,没有深度)

    高分回答

    "Spring AOP 的核心名词体系包括:

    1. 切面(Aspect) :横切关注点的模块化封装,包含通知和切入点。在 Spring 中通过 @Aspect 注解标识,由 @Order 控制多切面优先级。
    2. 连接点(Join Point) :程序执行过程中的特定点,Spring AOP 中仅限于方法调用JoinPoint 对象封装了目标对象、代理对象、方法签名、参数等信息;ProceedingJoinPoint 是其子接口,专用于 @Around 通知,提供 proceed() 控制目标方法执行。
    3. 切入点(Pointcut) :匹配连接点的表达式,使用 AspectJ 语法。核心指示器包括 execution(方法匹配)、@annotation(注解匹配)、within(类型匹配)、bean(Bean 名称匹配)等,支持 &&||! 组合。
    4. 通知(Advice) :切面在连接点执行的动作,共五种类型:
      • @Before:方法执行前
      • @After:方法执行后(无论是否异常,类似 finally)
      • @AfterReturning:方法正常返回后(可获取返回值)
      • @AfterThrowing:方法抛出异常后(可获取异常)
      • @Around:环绕方法执行,完全控制流程(必须调用 proceed()
    5. 织入(Weaving) :将切面应用到目标对象的过程。Spring AOP 采用运行期织入 (动态代理),通过 AbstractAutoProxyCreator 在 Bean 初始化后检查切点匹配,创建代理对象替换原始 Bean。
    6. 目标对象(Target):被代理的原始对象。
    7. 代理(Proxy):AOP 生成的代理对象,客户端实际持有的引用,负责拦截方法调用并执行通知链。

    理解这些名词的关键是建立'在哪里(Pointcut)→ 做什么(Advice)→ 怎么做(Proxy/Weaving)'的完整链路。"

  • 追问 2:"五种通知类型的执行顺序是什么?异常时怎么执行?"

    高分回答

    "正常执行顺序:

    复制代码
    @Around 前半 → @Before → 目标方法 → @AfterReturning → @After → @Around 后半

    异常执行顺序:

    复制代码
    @Around 前半 → @Before → 目标方法(抛异常)→ @AfterThrowing → @After → @Around 后半(异常传播)

    关键要点:

    • @After 类似 finally无论是否异常都会执行
    • @AfterReturning@AfterThrowing 互斥,只会执行其一;
    • @Around 中如果 proceed() 抛出异常,异常会传播到 @Around 的后半部分,可以在 @Around 中捕获并处理;
    • 多切面通过 @Order 控制顺序,数值越小优先级越高,@Before 先执行但 @After 后执行。"
  • 追问 3:"JoinPoint 和 ProceedingJoinPoint 有什么区别?"

    高分回答

    "ProceedingJoinPointJoinPoint 的子接口,核心区别:

    特性 JoinPoint ProceedingJoinPoint
    使用场景 所有通知类型(除 @Around @Around 通知
    控制目标方法 ❌ 无法控制 ✅ 通过 proceed() 控制执行
    修改参数 proceed(Object[] args)
    修改返回值 ✅ 可替换 proceed() 的返回值
    阻止执行 ✅ 不调用 proceed() 即可阻止

    JoinPoint 提供只读信息(目标对象、方法签名、参数等);ProceedingJoinPoint 增加了 proceed() 方法,这是 @Around 通知能够'环绕'目标方法的核心。

    ⚠️ 常见错误 :在 @Around 中忘记调用 proceed(),导致目标方法不执行。"

  • *追问 4:"切点表达式 execution( com.service..(...)) 每个部分是什么意思?"**

    高分回答

    "execution(* com.service.*.*(..)) 的语法结构是:

    execution(修饰符 返回类型 包名.类名.方法名(参数) 异常)

    逐部分解析:

    • execution:切点指示器,按方法签名匹配;
    • 第一个 *:返回类型,表示任意返回类型;
    • com.service:包名,精确匹配 com.service 包;
    • 第二个 *:类名,表示任意类;
    • 第三个 *:方法名,表示任意方法;
    • (..):参数,表示任意参数(零个或多个)。

    整体含义:匹配 com.service 包下任意类的任意方法,不限返回类型和参数。

    常用通配符:

    • *:匹配一个任意字符(包名中用一个点,类名/方法名中匹配任意);
    • ..:匹配零个或多个任意字符(包名中表示任意子包,参数中表示任意参数);
    • +:匹配当前类及其子类。"
  • 追问 5:"Spring AOP 为什么只能在运行时织入?AspectJ 是怎么织入的?"

    高分回答

    "Spring AOP 选择运行期织入(动态代理)的原因:

    1. 纯 Java 实现:无需修改编译器或 JVM,与 Spring IoC 无缝集成;
    2. 使用简单:注解驱动,零配置;
    3. 满足大多数需求:企业应用 95% 的 AOP 需求是方法拦截。

    AspectJ 支持三种织入时机:

    1. 编译期织入:使用 AspectJ 编译器(AJC)在编译时修改源码字节码,无运行时开销;
    2. 加载期织入(LTW) :通过 -javaagent 和特殊类加载器在类加载时修改字节码;
    3. 运行期织入:与 Spring AOP 类似。

    Spring AOP 的织入流程:Bean 初始化后 → AbstractAutoProxyCreator.postProcessAfterInitialization() 检查切点匹配 → 匹配则创建代理对象(JDK/CGLIB)→ 代理对象替换原始 Bean 注入到依赖方。

    代价:每次方法调用经过代理,有一定性能开销。但方法调用本身的 overhead 远大于代理开销,通常可忽略。"

  • 追问 6:"@Around 通知中 proceed() 的作用是什么?不调用会怎样?"

    高分回答

    "proceed()@Around 通知的核心方法,作用是执行目标方法(或下一个通知)。

    @Around 中,proceed() 的调用位置决定了通知的'环绕'特性:

    java 复制代码
    @Around("serviceMethods()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        // 前置逻辑(proceed() 之前)
        System.out.println("Before");
        
        Object result = pjp.proceed();  // ← 执行目标方法
        // 也可以传参:pjp.proceed(new Object[]{arg1, arg2});
        
        // 后置逻辑(proceed() 之后)
        System.out.println("After");
        return result;
    }

    不调用 proceed() 的后果

    • 目标方法不会执行
    • 如果这是事务切面,事务不会开启
    • 如果这是日志切面,只会记录'方法开始',不会记录'方法结束'。

    高级用法

    • 修改参数:pjp.proceed(new Object[]{modifiedArg})
    • 修改返回值:在 proceed() 后修改 result
    • 异常处理:用 try-catch 包裹 proceed(),捕获并转换异常。"

9. 方案选型速查表
业务场景 推荐通知类型 核心理由
权限校验 @Before 方法执行前拦截,失败直接拒绝
日志记录 @Around 精确计算耗时,记录入参和出参
事务管理 @Around 完全控制事务边界,可捕获异常回滚
缓存管理 @Around 先查缓存,无则执行并写入
异常告警 @AfterThrowing 仅异常时触发,避免正常流程干扰
资源释放 @After 类似 finally,确保一定执行
返回值处理 @AfterReturning 正常返回时处理,如数据脱敏
方法重试 @Around 捕获异常后循环重试

💡 面试官想要的满分总结

Spring AOP 的七大核心名词构成了完整的 AOP 概念体系:

切面(Aspect) 是横切关注点的模块化封装,包含通知和切入点;连接点(Join Point) 是程序执行的特定点,Spring 中仅限方法调用;切入点(Pointcut) 通过 AspectJ 表达式定义"在哪里"织入;通知(Advice) 定义"做什么",共五种类型按 @Around@Before → 目标方法 → @AfterReturning/@AfterThrowing@After 的顺序执行;织入(Weaving) 是应用切面的过程,Spring 选择运行期动态代理实现;目标对象(Target) 是被代理的原始对象;代理(Proxy) 是客户端实际持有的代理对象。

理解这些名词的关键是建立从"概念"到"源码"的链路:切点表达式由 AspectJExpressionPointcut 解析,通知执行由 ReflectiveMethodInvocation 驱动,代理创建由 DefaultAopProxyFactory 根据目标类是否实现接口选择 JDK 或 CGLIB。

生产中最容易踩的坑是 @Around 中忘记调用 proceed() 导致目标方法不执行,以及混淆 JoinPoint(只读)和 ProceedingJoinPoint(可控)的适用场景。记住:@Around 是最强大的通知类型,也是最容易出错的类型。


觉得对您有帮助,麻烦 点点关注啦 ,您的关注是我创作的最大动力~ 🎯