【Spring全家桶】Spring Framework核心:AOP:静态代理 vs 动态代理(JDK动态代理 vs CGLIB)、AOP核心概念、应用场景(附《思维导图》+《面试高频考点清单》)

简介: Spring AOP是Spring核心支柱之一,基于代理模式(JDK/CGLIB)实现面向切面编程,解耦横切关注点(日志、事务、权限等)。涵盖核心概念(切面、切入点、通知等)、代理机制、注解/XML配置及典型应用场景,助力构建高内聚、低耦合的企业级应用。

思维导图

Spring AOP 系统性知识体系

一、AOP 基础

1.1 AOP 定义与核心思想

面向切面编程(Aspect-Oriented Programming) 是一种编程范式,它通过横切关注点的分离,解决了面向对象编程(OOP)中代码分散和代码纠缠的问题。

  • 核心思想:将业务逻辑中与核心功能无关的通用功能(如日志、事务、权限、监控等)提取出来,形成独立的切面,在运行时动态地将这些切面织入到业务代码的指定位置
  • 解决的问题
    • 代码冗余:相同逻辑重复出现在多个业务方法中
    • 耦合度高:通用逻辑与业务逻辑紧密耦合,难以维护
    • 扩展性差:新增通用功能需要修改所有相关业务代码

1.2 AOP 与 OOP 的关系

  • 互补关系:AOP 不是 OOP 的替代品,而是 OOP 的补充
  • OOP:擅长处理纵向的业务逻辑分层(Controller→Service→DAO)
  • AOP:擅长处理横向的通用功能抽取(跨越多个业务模块的相同逻辑)

1.3 AOP在Spring全家桶中的地位

AOP是Spring Framework的两大核心支柱之一(另一个是IOC),它为Spring生态中的众多组件提供了基础支撑:

  • Spring声明式事务管理
  • Spring Cache缓存抽象
  • Spring Security安全框架
  • Spring Boot的自动配置与监控
  • 各种企业级应用的非功能性需求实现

    二、代理模式:AOP 的底层基石

    AOP的核心实现机制是代理模式。代理模式通过引入一个代理对象来控制对目标对象的访问,在不修改目标对象代码的前提下,为其添加额外功能。

2.1 静态代理

定义

静态代理是在编译期就已经确定代理类与目标类的关系,代理类由程序员手动编写或工具生成。

实现步骤

  1. 定义业务接口
  2. 编写目标类实现业务接口
  3. 编写代理类实现相同的业务接口
  4. 在代理类中持有目标对象的引用
  5. 在代理方法中调用目标方法并添加额外逻辑

代码示例

// 1. 业务接口
public interface UserService {
   
    void addUser(String username);
}

// 2. 目标类
public class UserServiceImpl implements UserService {
   
    @Override
    public void addUser(String username) {
   
        System.out.println("添加用户:" + username);
    }
}

// 3. 静态代理类
public class UserServiceProxy implements UserService {
   
    private UserService target;

    public UserServiceProxy(UserService target) {
   
        this.target = target;
    }

    @Override
    public void addUser(String username) {
   
        // 前置增强
        System.out.println("记录日志:开始执行addUser方法");
        // 调用目标方法
        target.addUser(username);
        // 后置增强
        System.out.println("记录日志:结束执行addUser方法");
    }
}

// 4. 使用
public class Main {
   
    public static void main(String[] args) {
   
        UserService target = new UserServiceImpl();
        UserService proxy = new UserServiceProxy(target);
        proxy.addUser("张三");
    }
}

优缺点

  • 优点
    • 简单直观,易于理解
    • 编译期生成,运行时性能好
    • 可以对特定方法进行精细控制
  • 缺点
    • 代码冗余:每个接口都需要编写对应的代理类
    • 维护成本高:接口方法变更时,代理类也需要同步修改
    • 灵活性差:无法在运行时动态创建代理

2.2 动态代理

定义

动态代理是在运行时根据需要动态生成代理类和代理对象,无需手动编写代理类。

分类

Spring AOP 提供了两种动态代理实现:JDK动态代理CGLIB动态代理

2.3 JDK动态代理

JDK动态代理是Java原生提供的动态代理实现,它基于接口实现,只能代理实现了至少一个接口的类。

核心原理

  • 基于接口实现,要求目标类必须实现至少一个接口
  • 由JDK内置的java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口实现
  • 运行时动态生成一个实现了目标类所有接口的代理类
  • 代理类会将所有方法调用转发给InvocationHandlerinvoke()方法处理

实现步骤

  1. 定义业务接口
  2. 编写目标类实现业务接口
  3. 编写调用处理器实现InvocationHandler接口
  4. invoke()方法中添加额外逻辑并调用目标方法
  5. 使用Proxy.newProxyInstance()方法创建代理对象

代码示例

// 1. 业务接口和目标类同上

// 2. 调用处理器
public class LogInvocationHandler implements InvocationHandler {
   
    private Object target;

    public LogInvocationHandler(Object target) {
   
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   
        // 前置增强
        System.out.println("JDK动态代理:记录日志,方法名:" + method.getName());
        // 调用目标方法
        Object result = method.invoke(target, args);
        // 后置增强
        System.out.println("JDK动态代理:方法执行完成");
        return result;
    }
}

// 3. 创建代理对象
public class Main {
   
    public static void main(String[] args) {
   
        UserService target = new UserServiceImpl();
        UserService proxy = (UserService) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),  // 类加载器
            target.getClass().getInterfaces(),   // 目标类实现的接口
            new LogInvocationHandler(target)     // 调用处理器
        );
        proxy.addUser("李四");
    }
}

优缺点

  • 优点
    • JDK原生支持,无需引入第三方依赖
    • 实现简单,性能较好(JDK 8+有显著优化)
    • 可以代理任意实现了接口的类
  • 缺点
    • 只能代理实现了接口的类,无法代理没有实现接口的类
    • 代理类会继承Proxy类,由于Java单继承限制,无法再继承其他类

2.4 CGLIB动态代理

CGLIB(Code Generation Library)是一个强大的高性能代码生成库,它基于继承实现,可以代理没有实现接口的类。

核心原理

CGLIB通过ASM字节码框架在运行时动态生成目标类的子类,并重写目标类的非final方法。当调用代理对象的方法时,会先调用MethodInterceptorintercept方法,然后再调用目标对象的方法。

  • 基于继承实现,不要求目标类实现接口
  • 使用第三方CGLIB(Code Generation Library)库实现
  • 运行时动态生成目标类的子类作为代理类
  • 代理类会重写目标类的所有非final方法
  • 通过方法拦截器MethodInterceptor来处理方法调用

实现步骤

  1. 编写目标类(无需实现接口)
  2. 编写方法拦截器实现MethodInterceptor接口
  3. intercept()方法中添加额外逻辑并调用目标方法
  4. 使用Enhancer类创建代理对象

代码示例

// 1. 目标类(无需实现接口)
public class UserService {
   
    public void addUser(String username) {
   
        System.out.println("添加用户:" + username);
    }
}

// 2. 方法拦截器
public class LogMethodInterceptor implements MethodInterceptor {
   
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
   
        // 前置增强
        System.out.println("CGLIB动态代理:记录日志,方法名:" + method.getName());
        // 调用目标方法(使用MethodProxy避免反射)
        Object result = proxy.invokeSuper(obj, args);
        // 后置增强
        System.out.println("CGLIB动态代理:方法执行完成");
        return result;
    }
}

// 3. 创建代理对象
public class Main {
   
    public static void main(String[] args) {
   
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserService.class);  // 设置父类(目标类)
        enhancer.setCallback(new LogMethodInterceptor());  // 设置回调

        UserService proxy = (UserService) enhancer.create();
        proxy.addUser("王五");
    }
}

优缺点

  • 优点
    • 可以代理没有实现接口的类,适用范围更广
    • 性能较好(Spring 3.2+集成了CGLIB,无需额外依赖)
    • 生成的代理类是目标类的子类,类型转换更自然
  • 缺点
    • 不能代理final类和final方法(因为无法继承和重写)
    • 生成代理类的速度比JDK动态代理慢
    • 需要依赖CGLIB库(Spring 3.2+已内置)

2.5 JDK动态代理 vs CGLIB动态代理 全面对比

对比维度 JDK动态代理 CGLIB动态代理
底层原理 基于接口实现 基于继承实现
代理限制 必须实现至少一个接口 不能是final类,方法不能是final
核心类 ProxyInvocationHandler EnhancerMethodInterceptor
方法调用方式 反射调用 直接调用(MethodProxy)
依赖 JDK原生,无需第三方依赖 Spring 3.2+内置,早期版本需引入cglib依赖
生成速度 较快,生成实现了目标接口的代理类 较慢,生成目标类的子类
运行性能 JDK 8+性能优秀,与CGLIB相当 早期版本性能优于JDK,JDK 8+差距缩小
代理范围 只能代理接口中定义的方法 可以代理所有非final方法
Spring默认选择 当目标类实现接口时 当目标类没有实现接口时

2.6 Spring AOP 代理选择策略

Spring AOP 会根据目标类是否实现接口自动选择代理方式:

  1. 默认策略
    • 如果目标类实现了至少一个接口,使用JDK动态代理
    • 如果目标类没有实现任何接口,使用CGLIB动态代理
  2. 强制使用CGLIB
    • 在Spring配置中设置proxy-target-class="true"
    • 在Spring Boot中设置spring.aop.proxy-target-class=true
  3. 注意事项
    • 当目标类实现了接口但强制使用CGLIB时,Spring会同时生成JDK代理和CGLIB代理
    • 代理对象只能赋值给接口类型(JDK代理)或目标类类型(CGLIB代理)

三、Spring AOP 核心概念

Spring AOP是基于代理模式实现的AOP框架,它提供了一套完整的AOP解决方案。以下是Spring AOP的核心概念:

3.1 核心术语概念

  1. 切面(Aspect):横切关注点的模块化,它包含了通知和切入点。例如,日志切面、事务切面。
  2. 连接点(Join Point):程序执行过程中的某个点,如方法调用、异常抛出等。Spring AOP只支持方法级别的连接点。
  3. 通知(Advice):切面在特定连接点执行的动作。通知有五种类型:
    • 前置通知(@Before):在目标方法执行前执行
    • 后置通知(@After):在目标方法执行后执行(无论是否抛出异常)
    • 返回通知(@AfterReturning):在目标方法正常返回后执行
    • 异常通知(@AfterThrowing):在目标方法抛出异常后执行
    • 环绕通知(@Around):在目标方法执行前后都执行,可以控制目标方法的执行
  4. 切入点(Pointcut):匹配连接点的表达式。它定义了哪些连接点需要被增强。
  5. 目标对象(Target Object):被代理的对象,也就是包含核心业务逻辑的对象。
  6. 代理对象(Proxy Object):AOP框架生成的对象,它包含了目标对象的所有方法和切面的增强逻辑。
  7. 织入(Weaving):将切面应用到目标对象并创建代理对象的过程。织入有三种时机:
    • 编译期织入:在编译时将切面代码编译到目标类中(如AspectJ)
    • 类加载期织入:在类加载时将切面代码织入到目标类中(如AspectJ 5+)
    • 运行期织入:在运行时动态生成代理对象(Spring AOP默认方式)

      3.2 核心术语图解

      ┌─────────────────────────────────────────────────┐
      │                     Aspect                      │
      │  ┌─────────┐  ┌─────────┐  ┌─────────────────┐  │
      │  │  Pointcut │  │  Advice  │  │  Introduction  │  │
      │  └─────────┘  └─────────┘  └─────────────────┘  │
      └─────────────────────────────────────────────────┘
                           │
                           ▼
      ┌─────────────────────────────────────────────────┐
      │                     Join Point                  │
      │  (方法执行、异常抛出等程序执行过程中的点)        │
      └─────────────────────────────────────────────────┘
                           │
                           ▼
      ┌─────────────────────────────────────────────────┐
      │                     Weaving                     │
      │  (将切面织入到目标对象的过程)                    │
      └─────────────────────────────────────────────────┘
                           │
                           ▼
      ┌─────────────────────────────────────────────────┐
      │                     Proxy                       │
      │  (生成的代理对象)                                │
      └─────────────────────────────────────────────────┘
      

3.2 核心术语详解

1. 切面(Aspect)

  • 定义:横切关注点的模块化,包含了切点和通知的组合
  • 通俗理解:一个切面就是一个包含了"在什么地方"和"做什么"的类
  • 示例:日志切面、事务切面、权限切面

2. 连接点(Join Point)

  • 定义:程序执行过程中的某个特定点,如方法执行、异常抛出、字段访问等
  • Spring AOP支持的连接点:仅支持方法执行类型的连接点
  • 通俗理解:所有可以被切面拦截的方法都是连接点

3. 切点(Pointcut)

  • 定义:用于匹配连接点的表达式,定义了"在什么地方"应用通知
  • 作用:从所有连接点中筛选出需要增强的特定方法
  • 表达式类型
    • execution:匹配方法执行(最常用)
    • within:匹配指定类型内的方法
    • this:匹配代理对象类型的方法
    • target:匹配目标对象类型的方法
    • args:匹配参数类型符合要求的方法
    • @annotation:匹配带有指定注解的方法
  • 示例execution(* com.example.service.*.*(..)) 匹配service包下所有类的所有方法

4. 通知(Advice)

  • 定义:切面在特定连接点上执行的动作,定义了"做什么"
  • 通知类型
通知类型 执行时机 适用场景
前置通知(Before) 目标方法执行前 权限检查、日志记录
后置返回通知(AfterReturning) 目标方法正常返回后 结果处理、日志记录
后置异常通知(AfterThrowing) 目标方法抛出异常后 异常处理、日志记录
后置最终通知(After) 目标方法执行后(无论是否异常) 资源释放
环绕通知(Around) 目标方法执行前后 性能监控、事务管理

5. 引入(Introduction)

  • 定义:允许在不修改现有类的情况下,为其添加新的方法或字段
  • 作用:可以让代理对象实现额外的接口
  • 示例:为所有Service类添加一个getVersion()方法

6. 织入(Weaving)

  • 定义:将切面应用到目标对象并创建代理对象的过程
  • 织入时机
织入时机 说明 实现方式
编译期织入 在编译时将切面织入到目标类的字节码中 AspectJ编译器
类加载期织入 在类加载到JVM时将切面织入 AspectJ 5+的加载时织入(LTW)
运行期织入 在运行时动态生成代理对象 Spring AOP(JDK/CGLIB)

7. 目标对象(Target Object)

  • 定义:被一个或多个切面所通知的对象
  • 注意:目标对象永远是被代理的对象,而不是代理对象本身

8. 代理对象(Proxy Object)

  • 定义:AOP框架创建的对象,包含了目标对象的所有方法和切面的增强逻辑
  • 注意:在Spring容器中,我们获取到的Bean实际上是代理对象

3.4 切入点表达式语法

Spring AOP使用AspectJ的切入点表达式语言来定义切入点。最常用的是execution表达式:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
  • modifiers-pattern:方法修饰符(可选)
  • ret-type-pattern:返回值类型(必选)
  • declaring-type-pattern:类名(可选)
  • name-pattern:方法名(必选)
  • param-pattern:参数类型(必选)
  • throws-pattern:异常类型(可选)

常用示例

  • 匹配所有public方法:execution(public * *(..))
  • 匹配所有以"add"开头的方法:execution(* add*(..))
  • 匹配com.example.service包下所有类的所有方法:execution(* com.example.service.*.*(..))
  • 匹配com.example.service包及其子包下所有类的所有方法:execution(* com.example.service..*.*(..))

    四、Spring AOP 实现机制与方式

4.1 Spring AOP的代理选择规则

Spring AOP会根据目标对象是否实现了接口来自动选择使用JDK动态代理还是CGLIB动态代理:

  1. 如果目标对象实现了至少一个接口,Spring会默认使用JDK动态代理
  2. 如果目标对象没有实现任何接口,Spring会使用CGLIB动态代理
  3. 可以通过配置强制使用CGLIB动态代理:
    <aop:config proxy-target-class="true"/>
    
    或在Spring Boot中:
    spring.aop.proxy-target-class=true
    

4.2 Spring AOP的自调用问题

自调用问题是Spring AOP中最常见的问题之一。当一个类中的方法调用同一个类中的另一个方法时,被调用的方法不会被AOP增强。

原因分析

Spring AOP是基于代理的,只有通过代理对象调用的方法才会被增强。当使用this关键字调用同一个类中的方法时,实际上是调用了目标对象本身的方法,而不是代理对象的方法。

解决方案

  1. 使用ApplicationContext获取代理对象

    @Service
    public class UserService {
         
        @Autowired
        private ApplicationContext context;
    
        public void methodA() {
         
            // 获取代理对象
            UserService proxy = context.getBean(UserService.class);
            proxy.methodB(); // 这样methodB会被增强
        }
    
        @Transactional
        public void methodB() {
         
            // ...
        }
    }
    
  2. 使用AspectJ编译期织入
    AspectJ通过编译期织入直接修改目标类的字节码,不存在自调用问题。但需要引入AspectJ编译器,配置相对复杂。

4.3 基于XML配置的AOP

<!-- 配置目标对象 -->
<bean id="userService" class="com.example.service.UserServiceImpl"/>

<!-- 配置切面 -->
<bean id="logAspect" class="com.example.aspect.LogAspect"/>

<!-- 配置AOP -->
<aop:config>
    <!-- 定义切点 -->
    <aop:pointcut id="servicePointcut" 
                  expression="execution(* com.example.service.*.*(..))"/>

    <!-- 定义切面 -->
    <aop:aspect ref="logAspect">
        <!-- 前置通知 -->
        <aop:before pointcut-ref="servicePointcut" method="before"/>
        <!-- 后置返回通知 -->
        <aop:after-returning pointcut-ref="servicePointcut" method="afterReturning" returning="result"/>
        <!-- 环绕通知 -->
        <aop:around pointcut-ref="servicePointcut" method="around"/>
    </aop:aspect>
</aop:config>

4.4 基于注解的AOP(推荐)

// 1. 开启AOP注解支持
@Configuration
@EnableAspectJAutoProxy  // 等价于<aop:aspectj-autoproxy/>
public class AopConfig {
   
}

// 2. 定义切面
@Aspect
@Component
public class LogAspect {
   

    // 定义切点
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void servicePointcut() {
   }

    // 前置通知
    @Before("servicePointcut()")
    public void before(JoinPoint joinPoint) {
   
        System.out.println("前置通知:" + joinPoint.getSignature().getName());
    }

    // 后置返回通知
    @AfterReturning(pointcut = "servicePointcut()", returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result) {
   
        System.out.println("后置返回通知:结果=" + result);
    }

    // 环绕通知
    @Around("servicePointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
   
        long start = System.currentTimeMillis();
        Object result = joinPoint.proceed();  // 执行目标方法
        long end = System.currentTimeMillis();
        System.out.println("方法执行时间:" + (end - start) + "ms");
        return result;
    }
}

五、Spring AOP 应用场景

5.1 核心应用场景

1. 日志记录

  • 记录方法调用信息(方法名、参数、返回值、执行时间)
  • 记录系统操作日志(谁在什么时间做了什么操作)
  • 记录异常日志(异常信息、堆栈跟踪)

2. 事务管理

  • Spring声明式事务的底层就是AOP
  • 在方法执行前开启事务,执行后提交事务,异常时回滚事务
  • 无需在业务代码中编写事务管理代码

3. 权限控制

  • 在方法执行前检查用户是否具有相应的权限
  • 统一的权限验证逻辑,避免在每个业务方法中重复编写
  • 支持细粒度的权限控制(方法级别的权限)

4. 性能监控

  • 统计方法的执行时间
  • 监控系统的性能瓶颈
  • 生成性能报告

5. 异常处理

  • 统一处理系统中的异常
  • 将异常转换为统一的响应格式
  • 记录异常信息并发送告警

6. 缓存管理

  • 在方法执行前检查缓存中是否有结果
  • 如果有则直接返回缓存结果,否则执行方法并将结果存入缓存
  • 统一的缓存逻辑,简化业务代码

5.2 其他应用场景

  • 数据校验
  • 分布式锁
  • 接口限流
  • 接口幂等性保证
  • 审计日志
  • 资源池管理

六、Spring AOP 常见问题与最佳实践

6.1 常见问题

1. 内部方法调用无法被拦截

  • 问题原因:当目标对象内部的方法A调用方法B时,方法B的调用是通过this引用进行的,而不是通过代理对象
  • 解决方案
    • 方法1:使用AopContext.currentProxy()获取代理对象
    • 方法2:将方法B移到另一个类中
    • 方法3:使用AspectJ编译期织入

2. final方法无法被代理

  • 问题原因:CGLIB无法重写final方法,JDK动态代理也无法代理接口中没有的final方法
  • 解决方案:避免在需要被代理的类中使用final方法

3. 代理对象无法注入到目标对象中

  • 问题原因:循环依赖导致代理对象创建失败
  • 解决方案
    • 使用@Lazy注解延迟加载
    • 重构代码避免循环依赖

6.2 最佳实践

  1. 优先使用注解方式:基于注解的AOP配置更加简洁、易读、易维护
  2. 切面职责单一:一个切面只负责一个横切关注点
  3. 切点表达式精确:避免使用过于宽泛的切点表达式,减少不必要的拦截
  4. 优先使用JDK动态代理:当目标类实现接口时,优先使用JDK动态代理
  5. 避免在通知中修改参数或返回值:除非必要,否则不要修改方法的参数和返回值
  6. 使用环绕通知时必须调用proceed():否则目标方法不会被执行
  7. 处理异常:在通知中妥善处理异常,避免影响业务逻辑的正常执行
  8. 优先使用注解方式配置AOP:注解方式比XML方式更简洁,易于维护
  9. 使用@Order注解指定切面执行顺序:当有多个切面时,使用@Order注解可以明确指定它们的执行顺序

七、Spring AOP 与 AspectJ 的对比

对比维度 Spring AOP AspectJ
实现方式 动态代理(JDK/CGLIB) 字节码操作(编译期/类加载期)
连接点支持 仅支持方法执行 支持所有连接点(方法、构造器、字段、异常等)
性能 运行时生成代理,性能略低 编译期织入,性能更高
复杂度 简单易用,学习成本低 功能强大,学习成本高
集成Spring 原生集成 需要额外配置
适用场景 简单的横切关注点 复杂的横切关注点

八、总结

Spring AOP 是 Spring Framework 的核心组件之一,它基于代理模式实现了面向切面编程的思想。通过将通用功能从业务逻辑中分离出来,AOP 极大地提高了代码的可维护性和扩展性。

Spring AOP 提供了两种动态代理实现:JDK动态代理(基于接口)和CGLIB动态代理(基于继承),并会根据目标类的情况自动选择合适的代理方式。

理解 AOP 的核心概念(切面、连接点、切点、通知、织入)是掌握 Spring AOP 的关键。在实际开发中,AOP 广泛应用于日志记录、事务管理、权限控制、性能监控等场景,是构建企业级应用不可或缺的技术。


Spring AOP 面试高频问答卡片(背诵版)

基础概念篇

Q1:什么是AOP?它的核心思想是什么?解决了什么问题?

A

  • 定义:面向切面编程(Aspect-Oriented Programming)是一种编程范式,通过横切关注点的分离解决OOP中的代码分散和纠缠问题
  • 核心思想:将业务逻辑中与核心功能无关的通用功能(日志、事务、权限、监控等)提取为独立切面,在运行时动态织入到业务代码的指定位置
  • 解决的问题
    1. 代码冗余:相同逻辑重复出现在多个业务方法中
    2. 耦合度高:通用逻辑与业务逻辑紧密耦合,难以维护
    3. 扩展性差:新增通用功能需要修改所有相关业务代码

Q2:AOP与OOP是什么关系?各自擅长处理什么问题?

A

  • 关系:AOP不是OOP的替代品,而是OOP的互补技术
  • OOP:擅长处理纵向的业务逻辑分层(Controller→Service→DAO)
  • AOP:擅长处理横向的通用功能抽取(跨越多个业务模块的相同逻辑)

Q3:AOP在Spring全家桶中的地位是什么?

A:AOP是Spring Framework的两大核心支柱之一(另一个是IOC),为Spring生态众多组件提供基础支撑:

  • Spring声明式事务管理
  • Spring Cache缓存抽象
  • Spring Security安全框架
  • Spring Boot的自动配置与监控
  • 各种企业级应用的非功能性需求实现

代理模式篇(面试核心)

Q4:静态代理和动态代理有什么区别?

A

对比维度 静态代理 动态代理
代理类生成时机 编译期确定,手动编写或工具生成 运行时动态生成,无需手动编写
代码量 每个接口都需要编写代理类,代码冗余 一个处理器可代理多个类,代码简洁
维护成本 接口变更时代理类需同步修改 接口变更不影响代理逻辑
灵活性 无法在运行时动态创建代理 可以在运行时动态创建任意代理
性能 编译期生成,运行时性能好 运行时生成代理类,性能略低

Q5:JDK动态代理的实现原理是什么?有什么优缺点?

A

  • 核心原理
    1. 基于接口实现,要求目标类必须实现至少一个接口
    2. 由JDK内置的Proxy类和InvocationHandler接口实现
    3. 运行时动态生成一个实现了目标类所有接口的代理类
    4. 所有方法调用都会转发给InvocationHandler.invoke()方法处理
  • 优点
    1. JDK原生支持,无需第三方依赖
    2. 实现简单,JDK 8+性能优秀
  • 缺点
    1. 只能代理实现了接口的类
    2. 代理类继承了Proxy类,受Java单继承限制

Q6:CGLIB动态代理的实现原理是什么?有什么优缺点?

A

  • 核心原理
    1. 基于继承实现,不要求目标类实现接口
    2. 使用ASM字节码框架在运行时动态生成目标类的子类
    3. 重写目标类的所有非final方法
    4. 通过MethodInterceptor.intercept()方法拦截方法调用
  • 优点
    1. 可以代理没有实现接口的类,适用范围更广
    2. Spring 3.2+已内置,无需额外依赖
  • 缺点
    1. 不能代理final类和final方法(无法继承和重写)
    2. 生成代理类的速度比JDK动态代理慢

Q7:JDK动态代理和CGLIB动态代理的核心区别是什么?

A

对比维度 JDK动态代理 CGLIB动态代理
底层原理 基于接口实现 基于继承实现
代理限制 必须实现至少一个接口 不能是final类,方法不能是final
核心类 ProxyInvocationHandler EnhancerMethodInterceptor
方法调用 反射调用 MethodProxy直接调用
生成速度 较快 较慢
运行性能 JDK 8+与CGLIB相当 早期版本优于JDK

Q8:Spring AOP默认的代理选择策略是什么?如何强制使用CGLIB代理?

A

  • 默认策略
    1. 如果目标类实现了至少一个接口,使用JDK动态代理
    2. 如果目标类没有实现任何接口,使用CGLIB动态代理
  • 强制使用CGLIB
    1. XML配置:<aop:config proxy-target-class="true"/>
    2. Spring Boot配置:spring.aop.proxy-target-class=true
    3. 注解配置:@EnableAspectJAutoProxy(proxyTargetClass=true)

核心概念篇

Q9:解释Spring AOP的7个核心概念

A

  1. 切面(Aspect):横切关注点的模块化,包含切点和通知的组合(如日志切面)
  2. 连接点(Join Point):程序执行过程中的点,Spring AOP仅支持方法执行类型
  3. 切点(Pointcut):匹配连接点的表达式,定义了"在什么地方"应用通知
  4. 通知(Advice):切面在特定连接点执行的动作,定义了"做什么"
  5. 目标对象(Target):被代理的对象,包含核心业务逻辑
  6. 代理对象(Proxy):AOP框架生成的对象,包含目标方法和增强逻辑
  7. 织入(Weaving):将切面应用到目标对象并创建代理对象的过程

Q10:Spring AOP支持哪五种通知类型?分别在什么时机执行?

A

通知类型 执行时机 适用场景
@Before 目标方法执行前 权限检查、参数校验
@AfterReturning 目标方法正常返回 结果处理、日志记录
@AfterThrowing 目标方法抛出异常 异常处理、告警通知
@After 目标方法执行后(无论是否异常) 资源释放
@Around 目标方法执行前后 性能监控、事务管理

Q11:织入有哪三种时机?Spring AOP默认使用哪种?

A

  1. 编译期织入:编译时将切面代码编译到目标类中(如AspectJ编译器)
  2. 类加载期织入:类加载到JVM时织入(如AspectJ LTW)
  3. 运行期织入:运行时动态生成代理对象
  • Spring AOP默认运行期织入(JDK/CGLIB动态代理)

Q12:最常用的execution切入点表达式语法是什么?举几个常用例子

A

  • 语法execution(修饰符? 返回值类型 类名? 方法名(参数) 异常?)
  • 常用示例
    1. 匹配所有public方法:execution(public * *(..))
    2. 匹配所有以"add"开头的方法:execution(* add*(..))
    3. 匹配service包下所有类的所有方法:execution(* com.example.service.*.*(..))
    4. 匹配service包及其子包下所有类的所有方法:execution(* com.example.service..*.*(..))
    5. 匹配带有@Transactional注解的方法:@annotation(org.springframework.transaction.annotation.Transactional)

实现机制与问题篇

Q13:Spring AOP的自调用问题是什么?产生的原因是什么?有哪些解决方案?

A

  • 问题描述:当一个类中的方法A调用同一个类中的方法B时,方法B不会被AOP增强
  • 根本原因:Spring AOP基于代理,只有通过代理对象调用的方法才会被增强。内部调用使用this关键字,指向的是目标对象本身而非代理对象
  • 解决方案
    1. 使用AopContext获取代理对象((UserService)AopContext.currentProxy()).methodB()
    2. 拆分方法到另一个类:将需要增强的方法移到独立的Bean中
    3. 使用AspectJ编译期织入:直接修改目标类字节码,不存在代理问题

Q14:为什么final方法无法被Spring AOP代理?

A

  • JDK动态代理:只能代理接口中定义的方法,而接口中不能有final方法
  • CGLIB动态代理:通过生成目标类的子类并重写方法实现代理,而final方法无法被重写
  • 因此,Spring AOP无法代理任何final方法

Q15:如何基于注解实现Spring AOP?请写出关键步骤

A

  1. 开启AOP注解支持:在配置类上添加@EnableAspectJAutoProxy
  2. 定义切面类:创建普通类,添加@Aspect@Component注解
  3. 定义切点:在方法上添加@Pointcut注解,指定切入点表达式
  4. 定义通知:在方法上添加对应的通知注解(@Before、@After等)
  5. 编写增强逻辑:在通知方法中实现具体的增强功能

核心代码示例

@Aspect
@Component
public class LogAspect {
   
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void servicePointcut() {
   }

    @Before("servicePointcut()")
    public void before(JoinPoint joinPoint) {
   
        System.out.println("方法执行前:" + joinPoint.getSignature().getName());
    }
}

应用与对比篇

Q16:Spring AOP有哪些核心应用场景?

A

  1. 日志记录:方法调用信息、操作日志、异常日志
  2. 事务管理:Spring声明式事务的底层实现
  3. 权限控制:方法级别的权限验证
  4. 性能监控:统计方法执行时间,定位性能瓶颈
  5. 异常处理:统一异常处理和转换
  6. 缓存管理:Spring Cache的底层实现
  7. 其他:数据校验、分布式锁、接口限流、幂等性保证

Q17:Spring AOP和AspectJ有什么区别?

A

对比维度 Spring AOP AspectJ
实现方式 动态代理(JDK/CGLIB) 字节码操作(编译期/类加载期)
连接点支持 仅支持方法执行 支持所有连接点(方法、构造器、字段、异常等)
性能 运行时生成代理,性能略低 编译期织入,性能更高
复杂度 简单易用,学习成本低 功能强大,学习成本高
集成Spring 原生集成 需要额外配置
适用场景 简单的横切关注点 复杂的横切关注点

Q18:Spring AOP的最佳实践有哪些?

A

  1. 优先使用注解方式配置AOP,简洁易维护
  2. 保持切面职责单一,一个切面只负责一个横切关注点
  3. 切点表达式尽量精确,避免使用过于宽泛的表达式
  4. 当目标类实现接口时,优先使用JDK动态代理
  5. 除非必要,不要在通知中修改方法参数或返回值
  6. 使用环绕通知时必须调用proceed()方法,否则目标方法不会执行
  7. 使用@Order注解明确指定多个切面的执行顺序
  8. 妥善处理通知中的异常,避免影响业务逻辑
相关文章
|
15天前
|
人工智能 自然语言处理 文字识别
阿里云百炼Qwen3.7-Max简介:能力、优势、支持订阅计划参考
Qwen3.7-Max是阿里云百炼面向智能体时代推出的新一代旗舰模型,对标GPT-5.5、Claude Opus 4.7等闭源旗舰。该模型支持百万级token上下文窗口,具备顶级推理能力、多模态搜索与视觉理解增强、流式输出低延迟响应等核心优势,覆盖编程、办公、长周期自主执行等复杂场景。同时支持OpenAI接口兼容,便于系统快速迁移。用户可通过Token Plan团队或节省计划等订阅方式灵活调用,适合企业级高要求场景使用。
5728 29
阿里云百炼Qwen3.7-Max简介:能力、优势、支持订阅计划参考
|
10天前
|
存储 定位技术 数据库
CodeGraph 如何让 Claude Code减少 7 成工具调用?
CodeGraph 为 Coding Agent 提供本地代码知识图谱,把函数、类、调用链和框架路由提前整理成“项目地图”,减少盲目搜索和文件读取。它不是新 Agent,而是上下文基础设施,让 Agent 更快找到正确代码路径,平均减少 7 成工具调用。
1165 2
|
7天前
|
人工智能 安全 定位技术
CodeGraph深度解析 让Claude Code工具调用直降七成的核心原理与实操教程
如今以Claude Code为代表的AI编程智能体已经成为开发者日常编码、项目重构、漏洞修复的必备工具。但在长期使用过程中,几乎所有开发者都会遇到同一个明显痛点:AI虽然具备强大的代码生成与分析能力,却常常陷入盲目探索的循环中。
927 1
|
17天前
|
人工智能 自然语言处理 供应链
|
7天前
|
人工智能 弹性计算 安全
阿里云618活动时间、活动入口、优惠活动详细解读
2026年阿里云618创新加速季已全面开启,作为年度力度最大的云产品促销活动,本次大促覆盖轻量应用服务器、ECS云服务器、GPU云服务器、数据库、AI算力、安全服务、CDN等全品类产品,推出5亿元算力补贴、新用户限时秒杀、普惠满减、企业专享、免费试用、云大使返佣等多重福利,个人开发者、中小企业、AI团队均可享受专属低价。本文将系统梳理2026年阿里云618活动的完整时间节点、官方参与入口、各类优惠细则、使用规则、热门产品推荐及实操代码,帮助用户精准参与、高效省钱,以最低成本完成上云部署。
704 3
|
23天前
|
人工智能 开发工具 iOS开发
Claude Code 新手完全上手指南:安装、国产模型配置与常用命令全解
Claude Code 是一款运行在终端环境中的 AI 编程助手,能够直接在命令行中完成代码生成、项目分析、文件修改、命令执行、Git 管理等开发全流程工作。它最大的特点是**任务驱动、终端原生、轻量高效、多模型兼容**,无需图形界面、不依赖 IDE 插件,能够深度融入开发者日常工作流。
3826 15
|
8天前
|
运维
欢迎报名|2026 Agentic AICon—智能体基础设施与AgentOps专场,邀您参会
欢迎报名|2026 Agentic AICon—智能体基础设施与AgentOps专场,邀您参会
1421 0