Skip to content

Latest commit

 

History

History
1519 lines (1188 loc) · 49.8 KB

File metadata and controls

1519 lines (1188 loc) · 49.8 KB

Spring AOP深度解析:代理模式与切面织入全链路源码分析

文章标签: #java #spring #aop #代理模式 #切面编程 #源码分析 #设计模式 #面试 #深度解析

目录


引言:为什么需要AOP

AOP(Aspect Oriented Programming,面向切面编程)不是简单的"代码拦截器",而是一种分离横切关注点的架构思想。在传统的OOP编程中,日志、事务、权限等横切关注点散落在业务代码各处,导致代码重复、难以维护。

核心认知:

传统OOP的问题:
┌─────────────────────────────────────────┐
│  UserService                            │
│  ├─ log.info("开始...")                 │
│  ├─ 事务开始                             │
│  ├─ 业务逻辑                             │
│  ├─ 事务提交                             │
│  ├─ log.info("结束...")                 │
│  └─ 权限检查                             │
├─────────────────────────────────────────┤
│  OrderService                           │
│  ├─ log.info("开始...")  ← 重复代码     │
│  ├─ 事务开始              ← 重复代码     │
│  ├─ 业务逻辑                             │
│  ├─ 事务提交              ← 重复代码     │
│  ├─ log.info("结束...")  ← 重复代码     │
│  └─ 权限检查              ← 重复代码     │
└─────────────────────────────────────────┘
问题:
- 代码重复(DRY原则被破坏)
- 业务逻辑与非业务逻辑混杂
- 修改横切逻辑需要修改多处

AOP的解决方式:
┌─────────────────────────────────────────┐
│  LoggingAspect(日志切面)               │
│  TransactionAspect(事务切面)           │
│  SecurityAspect(安全切面)              │
└─────────────────────────────────────────┘
         ↓ 织入(Weaving)
┌─────────────────────────────────────────┐
│  UserService    OrderService            │
│  ├─ 业务逻辑    ├─ 业务逻辑             │
│  └─ 业务逻辑    └─ 业务逻辑             │
└─────────────────────────────────────────┘
优势:
- 横切关注点集中管理
- 业务代码纯净
- 易于维护和修改

关键洞察:AOP的本质是关注点分离(Separation of Concerns),通过代理模式在不修改原始代码的情况下,动态添加横切功能。


理论基础:横切关注点的本质

1. 什么是横切关注点

横切关注点(Cross-cutting Concerns)是指分散在多个模块中、与核心业务逻辑无关但又必须存在的功能

横切关注点的分类:

1. 基础设施类
   - 日志记录(Logging)
   - 性能监控(Performance Monitoring)
   - 异常处理(Exception Handling)

2. 安全类
   - 身份认证(Authentication)
   - 权限控制(Authorization)
   - 数据校验(Validation)

3. 事务类
   - 事务管理(Transaction Management)
   - 数据一致性(Data Consistency)

4. 缓存类
   - 结果缓存(Caching)
   - 缓存失效(Cache Eviction)

5. 通信类
   - 消息发送(Messaging)
   - 事件发布(Event Publishing)

2. AOP的核心原理

AOP的实现基于代理模式字节码操作

AOP的核心机制:

┌─────────────────────────────────────────┐
│  目标对象(Target)                       │
│  ├─ methodA()                           │
│  ├─ methodB()                           │
│  └─ methodC()                           │
└─────────────────────────────────────────┘
              ↓ 代理包装
┌─────────────────────────────────────────┐
│  代理对象(Proxy)                        │
│  ├─ methodA()                           │
│  │   ├─ 前置通知(Before Advice)         │
│  │   ├─ 目标对象.methodA()               │
│  │   └─ 后置通知(After Advice)          │
│  ├─ methodB()                           │
│  │   ├─ 环绕通知(Around Advice)         │
│  │   │   ├─ 前置逻辑                     │
│  │   │   ├─ 目标对象.methodB()           │
│  │   │   └─ 后置逻辑                     │
│  │   └─ 返回通知(AfterReturning)        │
│  └─ methodC()                           │
│      └─ 异常通知(AfterThrowing)         │
└─────────────────────────────────────────┘

3. 织入(Weaving)的三种时机

织入时机对比:

1. 编译时织入(Compile-time Weaving)
   - 工具:AspectJ编译器(ajc)
   - 原理:在编译阶段修改源代码或字节码
   - 优点:运行时无性能开销
   - 缺点:需要特殊编译器

2. 加载时织入(Load-time Weaving)
   - 工具:Java Agent + AspectJ
   - 原理:在类加载时修改字节码
   - 优点:无需修改编译过程
   - 缺点:类加载时有性能开销

3. 运行时织入(Runtime Weaving)
   - 工具:Spring AOP
   - 原理:通过代理模式在运行时创建代理对象
   - 优点:简单、灵活
   - 缺点:有代理调用开销

来龙去脉:AOP技术的发展史

第一阶段:早期AOP探索(1997-2001)

Gregor Kiczales等人在Xerox PARC提出AOP概念,开发了AspectJ和AspectC++。

特点:

  • 全新的编程范式
  • 需要专门的编译器
  • 学习曲线陡峭

第二阶段:AspectJ成熟(2001-2004)

AspectJ成为Java AOP的事实标准:

// AspectJ语法
public aspect LoggingAspect {
    pointcut publicMethod(): execution(public * *.*(..));
    
    before(): publicMethod() {
        System.out.println("方法执行前: " + thisJoinPoint.getSignature());
    }
}

第三阶段:Spring AOP诞生(2004-2006)

Spring 1.0引入基于代理的AOP:

<!-- Spring 1.x AOP配置 -->
<aop:config>
    <aop:aspect ref="loggingAspect">
        <aop:before method="logBefore" pointcut="execution(* com.example.service.*.*(..))"/>
    </aop:aspect>
</aop:config>

特点:

  • 简化AOP使用
  • 与Spring IOC集成
  • 仅支持方法级别拦截

第四阶段:注解驱动(2006-2012)

Spring 2.0引入AspectJ注解支持:

@Aspect
@Component
public class LoggingAspect {
    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore(JoinPoint jp) {
        System.out.println("方法执行前: " + jp.getSignature().getName());
    }
}

第五阶段:现代Spring AOP(2012至今)

Spring Boot时代,AOP配置进一步简化:

@SpringBootApplication
@EnableAspectJAutoProxy
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

AOP核心概念详解

概念 说明
Aspect(切面) 横切关注点的模块化,包含通知和切点
Join Point(连接点) 程序执行过程中的点,如方法调用、异常抛出
Pointcut(切点) 匹配连接点的表达式
Advice(通知) 切面的具体动作,如何增强
Target(目标对象) 被代理的原始对象
Proxy(代理) AOP框架创建的对象
Weaving(织入) 将切面应用到目标对象

通知类型详解

@Aspect
@Component
public class LogAspect {
    
    // 前置通知:方法执行前
    @Before("execution(* com.example.service.*.*(..))")
    public void before(JoinPoint jp) {
        System.out.println("方法执行前: " + jp.getSignature().getName());
    }
    
    // 后置通知:方法执行后(无论是否异常)
    @After("execution(* com.example.service.*.*(..))")
    public void after() {
        System.out.println("方法执行后");
    }
    
    // 返回通知:方法正常返回后
    @AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
    public void afterReturning(Object result) {
        System.out.println("方法返回: " + result);
    }
    
    // 异常通知:方法抛出异常后
    @AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex")
    public void afterThrowing(Exception ex) {
        System.out.println("方法异常: " + ex.getMessage());
    }
    
    // 环绕通知:包围方法执行
    @Around("execution(* com.example.service.*.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕前");
        Object result = pjp.proceed(); // 执行目标方法
        System.out.println("环绕后");
        return result;
    }
}

通知执行顺序

单个切面的执行顺序:

@Around(前半部分)
  → @Before
    → 目标方法
  → @AfterReturning / @AfterThrowing
  → @After
  → @Around(后半部分)

多个切面的执行顺序(@Order控制):

Aspect1 @Order(1)          Aspect2 @Order(2)
  @Around(前半)             @Around(前半)
    @Before                    @Before
      目标方法
    @After                     @After
  @Around(后半)             @Around(后半)

注意:
- 数值越小优先级越高
- 优先级高的切面的@Before先执行
- 优先级高的切面的@After后执行(类似嵌套)

Spring AOP架构与源码深度分析

Spring AOP代理架构图

┌─────────────────────────────────────────────────────────────┐
│                    Spring AOP 代理架构                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   调用方                                                     │
│      │                                                      │
│      ▼                                                      │
│   ┌──────────────┐    ┌──────────────┐    ┌──────────────┐ │
│   │  Proxy代理   │───►│  Advisor链   │───►│ Target目标   │ │
│   │  (JDK/CGLIB) │    │  (拦截器链)   │    │  (原始Bean)  │ │
│   └──────────────┘    └──────────────┘    └──────────────┘ │
│                                                             │
│   Proxy = Target + Advice(s)                               │
│                                                             │
│   代理创建流程:                                              │
│   1. AbstractAutoProxyCreator.postProcessAfterInitialization │
│   2. 检查是否有匹配的Advisor                                 │
│   3. 创建ProxyFactory                                        │
│   4. 选择代理策略(JDK/CGLIB)                               │
│   5. 生成代理对象                                            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Spring AOP基于代理模式实现:

  1. JDK动态代理:目标类实现了接口
  2. CGLIB代理:目标类没有实现接口

默认策略:

  • 目标类实现了接口 → JDK代理
  • 目标类没有实现接口 → CGLIB代理

Spring Boot 2.x+默认全部使用CGLIB(proxyTargetClass=true)。


JDK动态代理源码深度分析

JDK动态代理原理

// java.lang.reflect.Proxy
public class Proxy implements java.io.Serializable {
    // 生成代理类的核心方法
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h) {
        // 1. 获取或生成代理类
        Class<?> cl = getProxyClass0(loader, intfs);
        
        // 2. 获取构造器
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        
        // 3. 创建实例
        return cons.newInstance(new Object[]{h});
    }
}

JDK动态代理完整示例

public class JdkProxyDemo {
    
    public interface UserService {
        void saveUser(String name);
        String getUser(Long id);
    }
    
    public static class UserServiceImpl implements UserService {
        @Override
        public void saveUser(String name) {
            System.out.println("保存用户: " + name);
        }
        
        @Override
        public String getUser(Long id) {
            return "User-" + id;
        }
    }
    
    public static 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("[日志] 开始调用: " + method.getName());
            System.out.println("[日志] 参数: " + Arrays.toString(args));
            long start = System.currentTimeMillis();
            
            Object result = method.invoke(target, args);
            
            long cost = System.currentTimeMillis() - start;
            System.out.println("[日志] 调用完成: " + method.getName() + ", 耗时: " + cost + "ms, 返回值: " + result);
            return result;
        }
    }
    
    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.saveUser("张三");
        String user = proxy.getUser(1L);
        System.out.println("返回结果: " + user);
        
        // 输出代理类名
        System.out.println("代理类: " + proxy.getClass().getName());
    }
}

Spring中的JDK动态代理实现

// JdkDynamicAopProxy - Spring JDK代理的核心实现
public class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {
    
    private final AdvisedSupport advised;
    
    public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {
        this.advised = config;
    }
    
    @Override
    public Object getProxy() {
        return getProxy(ClassUtils.getDefaultClassLoader());
    }
    
    @Override
    public Object getProxy(@Nullable ClassLoader classLoader) {
        // 获取代理要实现的接口
        Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
        // 生成代理对象
        return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
    }
    
    @Override
    @Nullable
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        MethodInvocation invocation;
        Object oldProxy = null;
        boolean setProxyContext = false;
        
        TargetSource targetSource = this.advised.targetSource;
        Object target = null;
        
        try {
            // 1. 处理equals/hashCode/toString等特殊方法
            if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
                return equals(args[0]);
            }
            
            // 2. 获取目标对象
            target = targetSource.getTarget();
            Class<?> targetClass = (target != null ? target.getClass() : null);
            
            // 3. 获取匹配的拦截器链(Advisor链)
            List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(
                method, targetClass);
            
            // 4. 如果没有拦截器,直接调用目标方法
            if (chain.isEmpty()) {
                Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
                retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
            } else {
                // 5. 创建MethodInvocation,执行拦截器链
                invocation = new ReflectiveMethodInvocation(proxy, target, method, args, 
                    targetClass, chain);
                // 执行拦截器链
                retVal = invocation.proceed();
            }
            
            return retVal;
        } finally {
            if (target != null && !targetSource.isStatic()) {
                targetSource.releaseTarget(target);
            }
        }
    }
}

CGLIB代理源码深度分析

CGLIB代理原理

CGLIB(Code Generation Library)通过生成目标类的子类来创建代理,可以代理没有实现接口的类。

// CglibAopProxy - Spring CGLIB代理的核心实现
public class CglibAopProxy implements AopProxy, Serializable {
    
    protected final AdvisedSupport advised;
    
    public CglibAopProxy(AdvisedSupport config) throws AopConfigException {
        this.advised = config;
    }
    
    @Override
    public Object getProxy() {
        return getProxy(null);
    }
    
    @Override
    public Object getProxy(@Nullable ClassLoader classLoader) {
        // 创建Enhancer
        Enhancer enhancer = createEnhancer();
        
        // 设置父类
        Class<?> rootClass = this.advised.getTargetClass();
        enhancer.setSuperclass(rootClass);
        
        // 设置接口
        enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
        
        // 设置回调
        Callback[] callbacks = getCallbacks(rootClass);
        enhancer.setCallbacks(callbacks);
        
        // 生成代理类并创建实例
        return createProxyClassAndInstance(enhancer, callbacks);
    }
}

CGLIB代理完整示例

public class CglibProxyDemo {
    
    public static class OrderService {
        public void createOrder(String orderId) {
            System.out.println("创建订单: " + orderId);
        }
        
        public String getOrder(String orderId) {
            return "Order-" + orderId;
        }
    }
    
    public static class LogMethodInterceptor implements MethodInterceptor {
        @Override
        public Object intercept(Object obj, Method method, Object[] args, 
                               MethodProxy proxy) throws Throwable {
            System.out.println("[日志] 开始: " + method.getName());
            System.out.println("[日志] 参数: " + Arrays.toString(args));
            long start = System.currentTimeMillis();
            
            Object result = proxy.invokeSuper(obj, args);
            
            long cost = System.currentTimeMillis() - start;
            System.out.println("[日志] 结束: " + method.getName() + ", 耗时: " + cost + "ms, 结果: " + result);
            return result;
        }
    }
    
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(OrderService.class);
        enhancer.setCallback(new LogMethodInterceptor());
        
        OrderService proxy = (OrderService) enhancer.create();
        proxy.createOrder("1001");
        String order = proxy.getOrder("1001");
        System.out.println("返回结果: " + order);
        
        // 输出代理类名
        System.out.println("代理类: " + proxy.getClass().getName());
        System.out.println("父类: " + proxy.getClass().getSuperclass().getName());
    }
}

切面织入时机与源码分析

Spring AOP是运行时织入(Runtime Weaving):

1. 加载BeanDefinition
2. 实例化Bean
3. 初始化Bean(调用init方法)
4. BeanPostProcessor.postProcessAfterInitialization
   - 检查是否需要代理(是否有切面匹配)
   - 创建代理对象(JDK或CGLIB)
   - 返回代理对象替代原始Bean
5. 容器中存储的是代理对象

AbstractAutoProxyCreator源码分析

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
        implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
    
    @Override
    public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
        if (bean != null) {
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            if (this.earlyProxyReferences.remove(cacheKey) != bean) {
                // 包装为代理对象(如果需要)
                return wrapIfNecessary(bean, beanName, cacheKey);
            }
        }
        return bean;
    }
    
    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        // 1. 获取所有Advisor
        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
        
        if (specificInterceptors != DO_NOT_PROXY) {
            this.advisedBeans.put(cacheKey, Boolean.TRUE);
            
            // 2. 创建代理
            Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, 
                new SingletonTargetSource(bean));
            
            this.proxyTypes.put(cacheKey, proxy.getClass());
            return proxy;
        }
        
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }
    
    protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
            @Nullable Object[] specificInterceptors, TargetSource targetSource) {
        
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.copyFrom(this);
        
        // 添加接口
        if (!proxyFactory.isProxyTargetClass()) {
            if (shouldProxyTargetClass(beanClass, beanName)) {
                proxyFactory.setProxyTargetClass(true);
            } else {
                evaluateProxyInterfaces(beanClass, proxyFactory);
            }
        }
        
        // 构建Advisor数组
        Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
        proxyFactory.addAdvisors(advisors);
        proxyFactory.setTargetSource(targetSource);
        
        // 自定义代理工厂
        customizeProxyFactory(proxyFactory);
        
        proxyFactory.setFrozen(this.freezeProxy);
        if (advisorsPreFiltered()) {
            proxyFactory.setPreFiltered(true);
        }
        
        // 创建代理
        return proxyFactory.getProxy(getProxyClassLoader());
    }
}

Advisor和Pointcut匹配

public class DefaultAdvisorAutoProxyCreator extends AbstractAdvisorAutoProxyCreator {
    
    @Override
    @Nullable
    protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, 
            @Nullable TargetSource targetSource) {
        // 1. 获取所有Advisor
        List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
        if (advisors.isEmpty()) {
            return DO_NOT_PROXY;
        }
        return advisors.toArray();
    }
    
    protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
        // 获取所有候选Advisor
        List<Advisor> candidateAdvisors = findCandidateAdvisors();
        // 筛选匹配的Advisor
        List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, 
            beanClass, beanName);
        extendAdvisors(eligibleAdvisors);
        if (!eligibleAdvisors.isEmpty()) {
            eligibleAdvisors = sortAdvisors(eligibleAdvisors);
        }
        return eligibleAdvisors;
    }
}

拦截器链执行源码

// ReflectiveMethodInvocation.proceed()
public Object proceed() throws Throwable {
    // 如果执行到拦截器链末尾,调用目标方法
    if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
        return invokeJoinpoint(); // 执行目标方法
    }
    
    Object interceptorOrInterceptionAdvice = 
        this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
    
    if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
        // 动态匹配
        InterceptorAndDynamicMethodMatcher dm = 
            (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
        if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
            return dm.interceptor.invoke(this);
        } else {
            return proceed(); // 不匹配,继续下一个
        }
    } else {
        // 静态匹配,直接执行
        return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
    }
}

设计模式解析

1. 代理模式(Proxy Pattern)

体现: JDK动态代理、CGLIB代理

为什么: 在不修改目标对象代码的情况下,添加额外功能。Spring通过代理拦截方法调用,在调用前后执行通知逻辑。

// 代理模式的核心:Proxy持有Target的引用,拦截调用
public class Proxy {
    private Target target;
    
    public void method() {
        before(); // 前置通知
        target.method(); // 调用目标
        after(); // 后置通知
    }
}

2. 装饰器模式(Decorator Pattern)

体现: Advisor链、MethodInterceptor链

为什么: 动态地给对象添加功能。多个Advisor可以叠加,每个Advisor装饰前一个Advisor的功能。

// 装饰器链
Advisor1Advisor2Advisor3Target
// 每个Advisor在执行目标前后添加自己的逻辑

3. 责任链模式(Chain of Responsibility)

体现: ReflectiveMethodInvocation执行的拦截器链

为什么: 将请求沿着处理者链传递,直到被处理。Spring的拦截器链按顺序执行,每个拦截器决定是否继续传递。

4. 策略模式(Strategy Pattern)

体现: AopProxy接口的不同实现(JdkDynamicAopProxyCglibAopProxy

为什么: 根据不同条件(是否实现接口)选择不同的代理策略。

public interface AopProxy {
    Object getProxy();
    Object getProxy(@Nullable ClassLoader classLoader);
}

// JdkDynamicAopProxy:基于接口
// CglibAopProxy:基于继承

5. 工厂模式(Factory Pattern)

体现: AopProxyFactory根据条件创建不同的代理对象

为什么: 将代理创建逻辑封装在工厂中,客户端无需关心具体实现。


实战案例:工业级AOP应用

案例1:完整AOP配置(日志+性能监控+异常处理)

@Aspect
@Component
public class LoggingAspect {
    
    private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
    
    // 定义切点:service包下所有方法
    @Pointcut("execution(* com.example.aop.demo.service.*.*(..))")
    public void serviceLayer() {}
    
    // 前置通知
    @Before("serviceLayer()")
    public void logBefore(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        logger.info("[LOG] 调用开始: {}.{}, 参数: {}",
            joinPoint.getTarget().getClass().getSimpleName(),
            methodName,
            Arrays.toString(args));
    }
    
    // 返回通知
    @AfterReturning(pointcut = "serviceLayer()", returning = "result")
    public void logAfterReturning(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        logger.info("[LOG] 调用成功: {}.{}, 返回: {}",
            joinPoint.getTarget().getClass().getSimpleName(),
            methodName,
            result);
    }
    
    // 异常通知
    @AfterThrowing(pointcut = "serviceLayer()", throwing = "ex")
    public void logAfterThrowing(JoinPoint joinPoint, Exception ex) {
        String methodName = joinPoint.getSignature().getName();
        logger.error("[LOG] 调用异常: {}.{}, 异常: {}",
            joinPoint.getTarget().getClass().getSimpleName(),
            methodName,
            ex.getMessage(),
            ex);
    }
    
    // 环绕通知(性能监控)
    @Around("serviceLayer()")
    public Object measurePerformance(ProceedingJoinPoint pjp) throws Throwable {
        String className = pjp.getTarget().getClass().getSimpleName();
        String methodName = pjp.getSignature().getName();
        long start = System.currentTimeMillis();
        
        try {
            Object result = pjp.proceed();
            long cost = System.currentTimeMillis() - start;
            if (cost > 1000) {
                logger.warn("[PERF] 慢查询: {}.{} 耗时: {}ms", className, methodName, cost);
            } else {
                logger.info("[PERF] {}.{} 耗时: {}ms", className, methodName, cost);
            }
            return result;
        } catch (Exception e) {
            long cost = System.currentTimeMillis() - start;
            logger.error("[PERF] {}.{} 耗时: {}ms (异常)", className, methodName, cost);
            throw e;
        }
    }
}

案例2:权限控制切面

@Aspect
@Component
public class SecurityAspect {
    
    @Around("@annotation(requireRole)")
    public Object checkRole(ProceedingJoinPoint pjp, RequireRole requireRole) throws Throwable {
        String currentRole = getCurrentUserRole(); // 从SecurityContext获取
        
        boolean hasRole = Arrays.stream(requireRole.value())
            .anyMatch(role -> role.equals(currentRole));
        
        if (!hasRole) {
            throw new AccessDeniedException("当前用户没有权限执行此操作");
        }
        
        return pjp.proceed();
    }
    
    private String getCurrentUserRole() {
        // 模拟获取当前用户角色
        return "ADMIN";
    }
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequireRole {
    String[] value();
}

案例3:缓存切面

@Aspect
@Component
public class CacheAspect {
    
    private final RedisTemplate<String, Object> redisTemplate;
    
    public CacheAspect(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
    
    @Around("@annotation(cacheable)")
    public Object cache(ProceedingJoinPoint pjp, Cacheable cacheable) throws Throwable {
        String key = cacheable.key() + "::" + generateKey(pjp);
        
        // 先查缓存
        Object value = redisTemplate.opsForValue().get(key);
        if (value != null) {
            System.out.println("[CACHE] 命中缓存: " + key);
            return value;
        }
        
        // 执行方法
        value = pjp.proceed();
        
        // 放入缓存
        redisTemplate.opsForValue().set(key, value, Duration.ofSeconds(cacheable.ttl()));
        System.out.println("[CACHE] 放入缓存: " + key);
        
        return value;
    }
    
    private String generateKey(ProceedingJoinPoint pjp) {
        return Arrays.stream(pjp.getArgs())
            .map(Object::toString)
            .collect(Collectors.joining("-"));
    }
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Cacheable {
    String key();
    long ttl() default 300; // 默认5分钟
}

案例4:分布式锁切面

@Aspect
@Component
public class DistributedLockAspect {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Around("@annotation(distributedLock)")
    public Object around(ProceedingJoinPoint pjp, DistributedLock distributedLock) throws Throwable {
        String lockKey = distributedLock.key();
        String lockValue = UUID.randomUUID().toString();
        int expireTime = distributedLock.expireTime();
        
        Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, expireTime, TimeUnit.SECONDS);
        
        if (!Boolean.TRUE.equals(locked)) {
            throw new RuntimeException("获取分布式锁失败: " + lockKey);
        }
        
        try {
            return pjp.proceed();
        } finally {
            // 释放锁(使用Lua脚本保证原子性)
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Collections.singletonList(lockKey), lockValue);
        }
    }
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DistributedLock {
    String key();
    int expireTime() default 30;
}

对比分析:Spring AOP vs 替代方案

Spring AOP vs AspectJ

特性 Spring AOP AspectJ
织入时机 运行时 编译时/加载时/运行时
实现方式 代理模式 字节码修改
性能 有代理开销 更高性能
功能范围 仅方法级别 方法、字段、构造器、异常处理
依赖 Spring框架 AspectJ编译器(ajc)
学习曲线
配置方式 注解/XML 注解/AspectJ语法

选择建议: 大多数场景Spring AOP足够;需要更高性能或更细粒度控制时用AspectJ。

Spring AOP vs Guice AOP

特性 Spring AOP Guice AOP
绑定方式 自动扫描+注解 显式绑定(Module)
切面定义 @Aspect注解 MethodInterceptor
代理方式 JDK/CGLIB CGLIB
功能丰富度 低(仅方法拦截)

Spring AOP vs CDI Interceptors

特性 Spring AOP CDI Interceptors
标准 Spring专属 JSR-318标准
绑定方式 切点表达式 @InterceptorBinding注解
适用场景 Spring生态 Java EE/Jakarta EE

性能分析与优化

1. 代理创建开销

// JDK代理和CGLIB代理都有创建开销
// 优化:避免对不需要代理的Bean创建代理

// 精确的切点表达式
@Pointcut("execution(* com.example.service.*.*(..)) && !execution(* com.example.service.*.get*(..))")
public void serviceWriteOps() {}
// 排除getter方法,减少代理创建

2. 方法匹配性能

// 静态切点(execution)在启动时匹配一次,性能较好
// 动态切点(如基于参数的匹配)每次调用都匹配,性能较差

// 避免动态切点
@Before("args(param) && @annotation(loggable)") // 动态匹配,性能较差
public void log(Object param, Loggable loggable) { }

// 使用静态切点
@Before("@annotation(loggable)") // 静态匹配,性能较好
public void log(JoinPoint jp, Loggable loggable) { }

3. 拦截器链长度

// 过多的Advisor会增加方法调用开销
// 优化:合并相似的切面

// 不要这样做
@Aspect
@Component
public class LogAspect1 { /* 记录日志 */ }

@Aspect
@Component
public class LogAspect2 { /* 记录参数 */ }

@Aspect
@Component
public class LogAspect3 { /* 记录返回值 */ }

// 合并为一个切面
@Aspect
@Component
public class UnifiedLogAspect { /* 统一日志处理 */ }

4. CGLIB vs JDK代理性能

特性 JDK代理 CGLIB
创建速度 慢(生成字节码)
调用性能 有反射开销 有方法索引开销
内存占用 较小 较大
适用场景 接口代理 类代理

建议: Spring Boot 2.x+默认使用CGLIB,一般情况下无需修改。

5. AOP性能监控

@Aspect
@Component
public class AopPerformanceMonitor {
    
    private static final Logger logger = LoggerFactory.getLogger(AopPerformanceMonitor.class);
    
    @Around("execution(* com.example.service.*.*(..))")
    public Object monitor(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        try {
            return pjp.proceed();
        } finally {
            long cost = System.currentTimeMillis() - start;
            if (cost > 500) {
                logger.warn("慢方法: {}.{} 耗时: {}ms",
                    pjp.getTarget().getClass().getSimpleName(),
                    pjp.getSignature().getName(),
                    cost);
            }
        }
    }
}

常见陷阱与最佳实践

陷阱1:同类方法调用导致AOP失效

@Service
public class UserService {
    public void methodA() {
        methodB(); // 同类调用,AOP失效!
    }
    
    @Transactional
    public void methodB() {
        // 事务不会生效
    }
}

最佳实践: 通过注入自身代理或从容器获取Bean调用。

@Service
public class UserService {
    @Autowired
    private UserService self; // 注入代理对象
    
    public void methodA() {
        self.methodB(); // 通过代理调用,AOP生效
    }
    
    @Transactional
    public void methodB() {
    }
}

陷阱2:代理对象暴露内部状态

@Aspect
@Component
public class SecurityAspect {
    @Around("execution(* com.example.service.*.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        // 如果目标方法返回this,拿到的是原始对象而非代理对象
        Object result = pjp.proceed();
        return result;
    }
}

最佳实践: 避免在AOP拦截的方法中返回this,或确保外部通过代理访问。

陷阱3:切点表达式过于宽泛

@Around("execution(* *(..))") // 拦截所有方法,性能灾难!
public Object around(ProceedingJoinPoint pjp) throws Throwable {
    // ...
}

最佳实践: 切点表达式尽量精确,限制包路径和类名,减少不必要的代理创建。

陷阱4:环绕通知忘记调用proceed()

@Around("execution(* com.example.service.*.*(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("环绕前");
    // 忘记调用 pjp.proceed(),目标方法不会执行!
    System.out.println("环绕后");
    return null;
}

最佳实践: 环绕通知必须调用pjp.proceed(),并正确处理返回值和异常。

陷阱5:final方法/类无法被代理

@Service
public final class FinalService { // final类不能被CGLIB代理
    @Transactional
    public final void method() { // final方法不能被代理
    }
}

最佳实践: 需要AOP增强的类和方法不要用final修饰。

陷阱6:忽视@Order注解

// 多个切面时,不指定顺序可能导致不可预期的行为
@Aspect
@Component
public class SecurityAspect { }

@Aspect
@Component
public class LoggingAspect { }

最佳实践: 明确指定切面顺序。

@Aspect
@Order(1) // 安全切面优先
@Component
public class SecurityAspect { }

@Aspect
@Order(2)
@Component
public class LoggingAspect { }

面试题与参考答案

Q1:JDK动态代理和CGLIB代理有什么区别?

答:

  • JDK动态代理:基于接口实现,要求目标类必须实现接口。生成的代理类实现相同接口,通过InvocationHandler拦截方法调用。优点是不依赖第三方库,缺点是只能代理接口方法。
  • CGLIB代理:基于继承实现,通过生成目标类的子类来创建代理。可以代理类的方法(包括非接口方法),但无法代理final类和final方法。需要依赖CGLIB库,Spring已内置。

Spring默认策略:目标类实现接口用JDK代理,否则用CGLIB。Spring Boot 2.x+默认全部使用CGLIB(proxyTargetClass=true)。

深度补充:

  • JDK代理通过反射调用目标方法
  • CGLIB通过方法索引(MethodProxy)调用,性能略优
  • CGLIB不能代理private方法

Q2:Spring AOP的通知执行顺序是怎样的?

答: 单个切面的执行顺序:

@Around(前半部分)
  → @Before
    → 目标方法
  → @AfterReturning / @AfterThrowing
  → @After
  → @Around(后半部分)

多个切面时,通过@Order注解或实现Ordered接口控制优先级,数值越小优先级越高。优先级高的切面的@Before先执行,@After后执行(类似嵌套)。

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

答:

特性 Spring AOP AspectJ AOP
织入时机 运行时织入 编译时/加载时/运行时
实现方式 代理模式(JDK/CGLIB) 字节码修改
性能 有代理开销 更高性能
功能 仅方法级别 方法、字段、构造器等
依赖 Spring框架 AspectJ编译器

Spring AOP是运行时基于代理的AOP,功能相对简单但易用;AspectJ是功能更完整的AOP框架,支持更细粒度的切面。

Q4:如何在一个切面中获取目标方法的参数和返回值?

答:

@Aspect
@Component
public class LogAspect {
    @Around("execution(* com.example.service.*.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        // 获取方法签名
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        String methodName = signature.getName();
        
        // 获取参数
        Object[] args = pjp.getArgs();
        
        // 执行目标方法并获取返回值
        Object result = pjp.proceed();
        
        // 记录日志
        log.info("方法:{},参数:{},返回值:{}", methodName, args, result);
        return result;
    }
}

Q5:Spring AOP在哪些场景下会失效?

答:

  1. 同类方法调用this.method()不走代理,AOP失效
  2. 非public方法:Spring AOP基于代理,无法拦截非public方法
  3. final方法/类:CGLIB通过继承实现,final方法无法重写
  4. 非Spring管理的Bean:AOP只对Spring容器中的Bean生效
  5. 异步方法@Async会在新线程执行,事务和AOP可能不生效

Q6:AOP代理是在Bean生命周期的哪个阶段生成的?

答: AOP代理在Bean初始化的最后阶段生成。具体是在BeanPostProcessor.postProcessAfterInitialization()方法中,由AbstractAutoProxyCreator实现。此时Bean已经完成属性赋值和所有初始化回调(@PostConstruct、InitializingBean等),Spring检查该Bean是否有匹配的Advisor,如果有则创建代理对象替换原始Bean。

深度补充:

  • AbstractAutoProxyCreator实现了SmartInstantiationAwareBeanPostProcessor
  • 代理创建时会检查earlyProxyReferences,避免重复代理
  • 如果Bean已经被提前暴露(解决循环依赖),会在getEarlyBeanReference中创建代理

Q7:什么是Advisor?Advisor和Advice有什么区别?

答:

  • Advice:通知,定义了切面要执行的逻辑(如@Before、@Around标记的方法)。
  • Advisor:顾问,是Advice和Pointcut的包装器,将通知和切点绑定在一起。Spring AOP内部使用Advisor来管理切面。
// Advisor = Advice + Pointcut
public interface Advisor {
    Advice getAdvice();
}

public interface PointcutAdvisor extends Advisor {
    Pointcut getPointcut();
}

Q8:如何自定义AOP代理创建?

答: 可以通过实现BeanPostProcessor或继承AbstractAutoProxyCreator来自定义代理创建逻辑。

@Component
public class CustomAutoProxyCreator extends AbstractAutoProxyCreator {
    @Override
    protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, 
            TargetSource customTargetSource) throws BeansException {
        // 自定义匹配逻辑
        if (beanClass.getName().startsWith("com.example.special")) {
            Advisor advisor = createCustomAdvisor();
            return new Object[]{advisor};
        }
        return DO_NOT_PROXY;
    }
    
    private Advisor createCustomAdvisor() {
        // 创建自定义Advisor
        return new DefaultPointcutAdvisor(new MyAdvice());
    }
}

Q9:Spring AOP如何支持循环依赖中的代理创建?

答: Spring通过getEarlyBeanReference方法在循环依赖解决阶段提前创建代理对象:

// AbstractAutowireCapableBeanFactory.getEarlyBeanReference()
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = 
                    (SmartInstantiationAwareBeanPostProcessor) bp;
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
    }
    return exposedObject;
}

Q10:如何排查AOP不生效的问题?

答:

  1. 检查Bean是否为代理对象:userService.getClass().getName()
  2. 检查@EnableAspectJAutoProxy是否添加
  3. 检查切面类是否有@Component
  4. 检查切点表达式是否匹配
  5. 检查目标类是否被Spring管理
  6. 开启DEBUG日志:logging.level.org.springframework.aop=DEBUG

Q11:AOP代理对象的类型有哪些?

答:

  • JDK动态代理:目标类实现接口时生成,代理类实现相同接口
  • CGLIB代理:目标类不实现接口时生成,代理类继承目标类
  • Spring Boot 2.x+默认全部使用CGLIB代理

Q12:@EnableAspectJAutoProxy的作用是什么?

答: @EnableAspectJAutoProxy注解用于开启Spring AOP的自动代理支持。它会向Spring容器中注册AnnotationAwareAspectJAutoProxyCreator,这是一个BeanPostProcessor,负责在Bean初始化后检查是否需要创建代理对象。

Q13:Spring AOP支持哪些类型的Advice?

答:

  • Before Advice:前置通知,方法执行前
  • After Returning Advice:返回通知,方法正常返回后
  • After Throwing Advice:异常通知,方法抛出异常后
  • After (Finally) Advice:后置通知,方法执行后(无论是否异常)
  • Around Advice:环绕通知,包围方法执行

Q14:AspectJ编译时织入和Spring AOP运行时织入有什么区别?

答:

  • AspectJ编译时织入:在编译阶段修改字节码,性能更好,支持更细粒度的切面(字段、构造器等),需要AspectJ编译器
  • Spring AOP运行时织入:通过代理模式在运行时创建代理对象,简单灵活,仅支持方法级别拦截,无需特殊编译器

Q15:如何在Spring AOP中获取方法注解信息?

答:

@Around("@annotation(loggable)")
public Object around(ProceedingJoinPoint pjp, Loggable loggable) throws Throwable {
    // 获取方法上的注解
    MethodSignature signature = (MethodSignature) pjp.getSignature();
    Method method = signature.getMethod();
    Loggable annotation = method.getAnnotation(Loggable.class);
    
    // 使用注解属性
    String value = annotation.value();
    // ...
    return pjp.proceed();
}

Q16:Spring AOP的代理对象可以转换为原始类型吗?

答: 不能直接转换。JDK代理实现的是接口,CGLIB代理继承的是类。如果需要获取原始对象,可以通过AopContext或AopProxyUtils:

// 方式1:AopContext
UserService target = (UserService) AopContext.currentProxy();

// 方式2:Advised接口
if (proxy instanceof Advised) {
    Object target = ((Advised) proxy).getTargetSource().getTarget();
}

Q17:Spring AOP如何处理泛型方法的切面?

答: Spring AOP基于运行时类型,对于泛型方法的切面匹配需要注意类型擦除。可以使用args()指示器进行运行时类型匹配:

@Around("execution(* com.example.service.*.*(..)) && args(param)")
public Object around(ProceedingJoinPoint pjp, Object param) throws Throwable {
    // param的类型是运行时实际类型
    return pjp.proceed();
}

Q18:什么是Introduction(引入)?Spring AOP支持吗?

答: Introduction是AOP的一种特殊Advice,允许为现有类添加新的接口实现。Spring AOP支持Introduction,通过@DeclareParents注解实现:

@Aspect
@Component
public class IntroductionAspect {
    @DeclareParents(value = "com.example.service.*", defaultImpl = AuditableImpl.class)
    private Auditable auditable;
}

此文原创,转载请注明出处。