面试官:什么是Spring AOP?它的作用是什么?实现方式有哪些?

48 阅读4分钟

 

Spring AOP(面向切面编程) 是Spring框架中用于解耦横切关注点(如日志、事务、安全等)的核心技术。它通过动态代理机制,在不修改业务代码的前提下,将通用逻辑“织入”到目标方法中。本文从源码层面解析Spring AOP的实现原理,并通过代码示例帮助读者彻底掌握它的核心概念和应用场景!


一、什么是Spring AOP?

1. 核心概念

  • AOP(Aspect-Oriented Programming) :面向切面编程,是一种编程范式。
  • 横切关注点:多个模块中重复的逻辑(如日志、事务、权限校验)。
  • 核心思想:将横切逻辑与业务逻辑分离,提高代码复用性和可维护性。

2. 为什么需要AOP?

假设一个业务系统中需要为所有Service方法添加日志和事务管理:

  • 传统方式:在每个方法中手动添加日志和事务代码,导致代码冗余。
  • AOP方式:通过切面统一处理,业务代码无需任何修改!

二、Spring AOP的核心组件

1. 切面(Aspect)

定义横切逻辑的模块。例如,一个日志切面包含记录方法执行时间的逻辑。

2. 连接点(Join Point)

程序执行过程中可插入切面的点。例如,方法调用、异常抛出等。

3. 通知(Advice)

切面在连接点执行的具体逻辑,分为五种类型:

  • @Before:方法执行前执行。
  • @After:方法执行后执行(无论是否抛出异常)。
  • @AfterReturning:方法正常返回后执行。
  • @AfterThrowing:方法抛出异常后执行。
  • @Around:包裹目标方法,可控制方法执行。

4. 切点(Pointcut)

通过表达式定义哪些连接点需要应用通知。例如,匹配所有Service层的方法。

5. 示例代码

@Aspect
@Component
public class LogAspect {
    // 定义切点:匹配所有Service层的方法
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void servicePointcut() {}

    // 定义通知:方法执行前打印日志
    @Before("servicePointcut()")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("调用方法: " + joinPoint.getSignature().getName());
    }
}


三、Spring AOP的实现方式

1. 基于代理的AOP(Spring原生实现)

实现原理

  • JDK动态代理:针对实现了接口的类,通过Proxy类生成代理对象。
  • CGLIB代理:针对未实现接口的类,通过生成子类继承目标类。

源码解析
Spring通过ProxyFactory类选择代理方式:

public class ProxyFactory {
    public Object getProxy() {
        if (isInterfaceProxyingEnabled()) {
            return createJdkDynamicProxy();  // JDK动态代理
        } else {
            return createCglibProxy();      // CGLIB代理
        }
    }
}

示例场景

  • 目标类实现了接口 → 使用JDK动态代理。
  • 目标类未实现接口 → 使用CGLIB代理。

2. 基于AspectJ的AOP

实现原理

  • 编译时织入(CTW) :在编译阶段修改字节码,直接生成包含切面逻辑的类。
  • 加载时织入(LTW) :在类加载时通过类加载器修改字节码。

与Spring AOP的区别

  • Spring AOP:运行时动态代理,仅支持方法级别的切面。
  • AspectJ:更强大,支持字段、构造器级别的切面,但需要额外配置。

四、Spring AOP的源码级执行流程

@Around通知为例,解析代理方法的执行过程:

  1. 生成代理对象:通过ProxyFactory创建代理类。
  2. 拦截方法调用:代理对象调用方法时,触发MethodInterceptor
  3. 执行通知链:按顺序执行所有通知(Before → Around → After等)。
  4. 反射调用目标方法:最终通过反射调用原始方法。

关键源码片段

public class JdkDynamicAopProxy implements InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) {
        // 获取拦截器链(即所有通知)
        List<MethodInterceptor> interceptors = getInterceptors(method);
        // 创建方法调用链
        MethodInvocation invocation = new ReflectiveMethodInvocation(...);
        // 执行拦截器链
        return invocation.proceed();
    }
}


五、Spring AOP的常见应用场景

1. 日志记录

统一记录方法的入参、返回值、执行时间。

@Around("servicePointcut()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
    long start = System.currentTimeMillis();
    Object result = joinPoint.proceed();
    long end = System.currentTimeMillis();
    System.out.println("方法执行耗时: " + (end - start) + "ms");
    return result;
}

2. 事务管理

通过@Transactional注解实现声明式事务。

@Transactional
public void transferMoney(Account from, Account to, double amount) {
    // 业务逻辑
}

3. 权限校验

在方法执行前验证用户权限。

@Before("servicePointcut() && args(user)")
public void checkPermission(User user) {
    if (!user.hasPermission()) {
        throw new SecurityException("无权限操作!");
    }
}


六、Spring AOP的局限性

  1. 仅支持方法级别的切面:无法拦截字段访问或构造器调用。
  2. 性能开销:动态代理在运行时生成,会有轻微性能损耗。
  3. 自调用问题:类内部方法相互调用时,AOP可能失效。

七、总结

  • 什么是Spring AOP:通过动态代理实现横切逻辑的解耦。

  • 核心作用:提升代码复用性,简化日志、事务等通用逻辑。

  • 实现方式

    • Spring原生代理:简单易用,适合大多数场景。
    • AspectJ:功能强大,适合复杂需求。