全部笔记All notes

Spring AOP 面向切面编程完整指南

阅读 10m 02s10m 02s read

概述

AOP(Aspect-Oriented Programming) 面向切面编程是一种编程范式,它将横切关注点(如日志记录、事务管理、权限检查等)从业务逻辑中分离出来,通过动态代理的方式在运行时将这些关注点织入到目标对象中。

核心特性

  • 横切关注点分离:将系统级功能与业务逻辑解耦
  • 动态代理:运行时动态创建代理对象
  • 非侵入式:无需修改原有代码即可添加功能
  • 声明式编程:通过配置或注解定义切面行为

应用场景

场景描述示例
日志记录记录方法调用日志记录用户操作、系统日志
事务管理声明式事务控制数据库事务、分布式事务
权限检查方法级权限验证用户认证、角色授权
性能监控方法执行时间统计性能分析、瓶颈识别
异常处理统一异常处理错误记录、异常转换

💡 提示: AOP 是 Spring 框架的核心特性之一,与 IoC 容器配合使用,可以构建松耦合的企业级应用。

AOP 核心概念

基本术语

术语英文描述示例
切面Aspect横切关注点的模块化,包含通知和切点LogAspect、TransactionAspect
连接点Joinpoint程序执行的特定点,通常是方法调用方法执行前、后、异常时
切点Pointcut连接点的集合,定义在哪里应用通知所有 Service 层方法
通知Advice在特定连接点执行的代码前置通知、后置通知
目标对象Target Object被一个或多个切面通知的对象UserService、OrderService
代理对象ProxyAOP 创建的对象,包含目标对象和通知UserServiceProxy
织入Weaving将切面应用到目标对象的过程编译时、类加载时、运行时

AOP 工作原理

目标对象 (Target) + 切面 (Aspect) = 代理对象 (Proxy)

客户端调用 → 代理对象 → 执行通知 → 目标对象 → 返回结果

代理模式基础

静态代理示例

代理模式是 AOP 的基础,让我们先通过一个生活化的例子理解代理的概念:

业务场景:代购服务

// 被代理对象:韩国商店
public class KoreanShop {
    public void sell() {
        System.out.println("韩国商店卖化妆品100元");
    }
}

// 代理对象:代购
public class ShopProxy {
    private KoreanShop shop;
    
    public ShopProxy(KoreanShop shop) {
        this.shop = shop;
    }
    
    public void sell() {
        // 前置增强:收取服务费
        System.out.println("代购收取10元服务费");
        
        // 调用目标对象方法
        shop.sell();
        
        // 后置增强:提供额外服务
        System.out.println("代购提供包装和快递服务");
        System.out.println("最终价格:110元");
    }
}

// 客户端使用
public class Client {
    public static void main(String[] args) {
        KoreanShop shop = new KoreanShop();
        ShopProxy proxy = new ShopProxy(shop);
        proxy.sell();
    }
}

输出结果:

代购收取10元服务费
韩国商店卖化妆品100元
代购提供包装和快递服务
最终价格:110元

代理模式优势

  • 功能增强:在不修改原有代码的基础上添加新功能
  • 职责分离:将核心业务逻辑与辅助功能分离
  • 灵活性:可以根据需要动态添加或移除功能

JDK 动态代理

基本原理

JDK 动态代理基于反射机制,为接口创建代理对象。代理对象实现了目标接口,通过 InvocationHandler 处理方法调用。

实现步骤

1. 定义接口和实现类

// 业务接口
public interface UserService {
    void save(String username);
    void update(String username);
    void delete(String username);
}

// 业务实现类
public class UserServiceImpl implements UserService {
    @Override
    public void save(String username) {
        System.out.println("保存用户:" + username);
    }
    
    @Override
    public void update(String username) {
        System.out.println("更新用户:" + username);
    }
    
    @Override
    public void delete(String username) {
        System.out.println("删除用户:" + username);
    }
}

2. 创建切面类

// 切面类:提供横切关注点功能
public class LogAspect {
    
    // 前置通知
    public void beforeAdvice(String methodName) {
        System.out.println("[前置通知] 调用方法:" + methodName + " - " + 
                          new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
    }
    
    // 后置通知
    public void afterAdvice(String methodName) {
        System.out.println("[后置通知] 方法执行完成:" + methodName);
    }
    
    // 异常通知
    public void exceptionAdvice(String methodName, Exception e) {
        System.out.println("[异常通知] 方法执行异常:" + methodName + ", 异常信息:" + e.getMessage());
    }
}

3. 实现 InvocationHandler

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class JdkProxyFactory implements InvocationHandler {
    
    private Object target;        // 目标对象
    private LogAspect logAspect;  // 切面对象
    
    public JdkProxyFactory(Object target) {
        this.target = target;
        this.logAspect = new LogAspect();
    }
    
    // 创建代理对象
    public Object createProxy() {
        return Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            this
        );
    }
    
    // 处理方法调用
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        Object result = null;
        
        try {
            // 前置通知
            logAspect.beforeAdvice(methodName);
            
            // 调用目标方法
            result = method.invoke(target, args);
            
            // 后置通知
            logAspect.afterAdvice(methodName);
            
        } catch (Exception e) {
            // 异常通知
            logAspect.exceptionAdvice(methodName, e);
            throw e;
        }
        
        return result;
    }
}

4. 测试代理功能

public class JdkProxyTest {
    public static void main(String[] args) {
        // 创建目标对象
        UserService target = new UserServiceImpl();
        
        // 创建代理工厂
        JdkProxyFactory factory = new JdkProxyFactory(target);
        
        // 创建代理对象
        UserService proxy = (UserService) factory.createProxy();
        
        // 通过代理对象调用方法
        proxy.save("张三");
        proxy.update("李四");
        proxy.delete("王五");
    }
}

输出结果:

[前置通知] 调用方法:save - 2025-07-09 19:10:00
保存用户:张三
[后置通知] 方法执行完成:save
[前置通知] 调用方法:update - 2025-07-09 19:10:00
更新用户:李四
[后置通知] 方法执行完成:update
[前置通知] 调用方法:delete - 2025-07-09 19:10:00
删除用户:王五
[后置通知] 方法执行完成:delete

CGLIB 动态代理

基本原理

CGLIB(Code Generation Library)通过字节码生成技术,为没有实现接口的类创建子类代理。

实现步骤

1. 添加 CGLIB 依赖

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

2. 创建目标类(无需接口)

// 目标类:不需要实现接口
public class OrderService {
    
    public void createOrder(String orderInfo) {
        System.out.println("创建订单:" + orderInfo);
    }
    
    public void updateOrder(String orderId) {
        System.out.println("更新订单:" + orderId);
    }
    
    public void cancelOrder(String orderId) {
        System.out.println("取消订单:" + orderId);
    }
}

3. 创建 CGLIB 代理工厂

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibProxyFactory implements MethodInterceptor {
    
    private Object target;
    private LogAspect logAspect;
    
    public CglibProxyFactory(Object target) {
        this.target = target;
        this.logAspect = new LogAspect();
    }
    
    // 创建代理对象
    public Object createProxy() {
        // 创建 Enhancer 对象
        Enhancer enhancer = new Enhancer();
        
        // 设置父类(目标类)
        enhancer.setSuperclass(target.getClass());
        
        // 设置回调函数
        enhancer.setCallback(this);
        
        // 创建代理对象
        return enhancer.create();
    }
    
    // 方法拦截
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        String methodName = method.getName();
        Object result = null;
        
        try {
            // 前置通知
            logAspect.beforeAdvice(methodName);
            
            // 调用目标方法
            result = proxy.invokeSuper(obj, args);
            
            // 后置通知
            logAspect.afterAdvice(methodName);
            
        } catch (Exception e) {
            // 异常通知
            logAspect.exceptionAdvice(methodName, e);
            throw e;
        }
        
        return result;
    }
}

4. 测试 CGLIB 代理

public class CglibProxyTest {
    public static void main(String[] args) {
        // 创建目标对象
        OrderService target = new OrderService();
        
        // 创建代理工厂
        CglibProxyFactory factory = new CglibProxyFactory(target);
        
        // 创建代理对象
        OrderService proxy = (OrderService) factory.createProxy();
        
        // 通过代理对象调用方法
        proxy.createOrder("iPhone 15 Pro");
        proxy.updateOrder("ORDER-12345");
        proxy.cancelOrder("ORDER-12345");
    }
}

Spring AOP 实现

XML 配置方式

1. 添加依赖

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.21</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>5.3.21</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.9</version>
    </dependency>
</dependencies>

2. 创建切面类

// 切面类
public class LoggingAspect {
    
    // 前置通知
    public void beforeAdvice(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        String className = joinPoint.getTarget().getClass().getSimpleName();
        System.out.println("[前置通知] " + className + "." + methodName + "() 开始执行");
    }
    
    // 后置通知
    public void afterAdvice(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        String className = joinPoint.getTarget().getClass().getSimpleName();
        System.out.println("[后置通知] " + className + "." + methodName + "() 执行完成");
    }
    
    // 环绕通知
    public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        
        long startTime = System.currentTimeMillis();
        System.out.println("[环绕通知] 方法开始执行:" + methodName);
        
        Object result = joinPoint.proceed();
        
        long endTime = System.currentTimeMillis();
        System.out.println("[环绕通知] 方法执行完成:" + methodName + 
                          ",耗时:" + (endTime - startTime) + "ms");
        
        return result;
    }
    
    // 异常通知
    public void exceptionAdvice(JoinPoint joinPoint, Exception e) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("[异常通知] 方法执行异常:" + methodName + 
                          ",异常信息:" + e.getMessage());
    }
}

3. Spring 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop.xsd">
    
    <!-- 配置目标对象 -->
    <bean id="userService" class="com.example.service.UserServiceImpl"/>
    
    <!-- 配置切面 -->
    <bean id="loggingAspect" class="com.example.aspect.LoggingAspect"/>
    
    <!-- AOP 配置 -->
    <aop:config>
        <!-- 定义切点 -->
        <aop:pointcut id="servicePointcut" 
                      expression="execution(* com.example.service.*.*(..))"/>
        
        <!-- 配置切面 -->
        <aop:aspect ref="loggingAspect">
            <!-- 前置通知 -->
            <aop:before method="beforeAdvice" pointcut-ref="servicePointcut"/>
            
            <!-- 后置通知 -->
            <aop:after method="afterAdvice" pointcut-ref="servicePointcut"/>
            
            <!-- 环绕通知 -->
            <aop:around method="aroundAdvice" pointcut-ref="servicePointcut"/>
            
            <!-- 异常通知 -->
            <aop:after-throwing method="exceptionAdvice" 
                               pointcut-ref="servicePointcut" 
                               throwing="e"/>
        </aop:aspect>
    </aop:config>
</beans>

注解配置方式

1. 启用 AOP 注解

@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.example")
public class AppConfig {
    // 配置类
}

2. 创建注解切面

@Aspect
@Component
public class LoggingAspect {
    
    // 定义切点
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void servicePointcut() {}
    
    // 前置通知
    @Before("servicePointcut()")
    public void beforeAdvice(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        String className = joinPoint.getTarget().getClass().getSimpleName();
        System.out.println("[前置通知] " + className + "." + methodName + "() 开始执行");
    }
    
    // 后置通知
    @After("servicePointcut()")
    public void afterAdvice(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        String className = joinPoint.getTarget().getClass().getSimpleName();
        System.out.println("[后置通知] " + className + "." + methodName + "() 执行完成");
    }
    
    // 环绕通知
    @Around("servicePointcut()")
    public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        
        long startTime = System.currentTimeMillis();
        System.out.println("[环绕通知] 方法开始执行:" + methodName);
        
        Object result = joinPoint.proceed();
        
        long endTime = System.currentTimeMillis();
        System.out.println("[环绕通知] 方法执行完成:" + methodName + 
                          ",耗时:" + (endTime - startTime) + "ms");
        
        return result;
    }
    
    // 异常通知
    @AfterThrowing(pointcut = "servicePointcut()", throwing = "e")
    public void exceptionAdvice(JoinPoint joinPoint, Exception e) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("[异常通知] 方法执行异常:" + methodName + 
                          ",异常信息:" + e.getMessage());
    }
}

AspectJ 集成

AspectJ 与 Spring AOP 对比

特性Spring AOPAspectJ
编译方式运行时代理编译时织入
性能较低高
功能完整性基础 AOP 功能完整 AOP 功能
学习成本低高
集成难度简单复杂

AspectJ 注解详解

通知类型

@Aspect
@Component
public class AspectJExample {
    
    // 前置通知
    @Before("execution(* com.example.service.*.*(..))")
    public void beforeAdvice(JoinPoint joinPoint) {
        // 方法执行前的逻辑
    }
    
    // 后置通知(无论是否异常都执行)
    @After("execution(* com.example.service.*.*(..))")
    public void afterAdvice(JoinPoint joinPoint) {
        // 方法执行后的逻辑
    }
    
    // 返回通知(正常返回时执行)
    @AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
    public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
        // 方法正常返回后的逻辑
    }
    
    // 异常通知
    @AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "e")
    public void afterThrowingAdvice(JoinPoint joinPoint, Exception e) {
        // 方法抛出异常时的逻辑
    }
    
    // 环绕通知
    @Around("execution(* com.example.service.*.*(..))")
    public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        // 方法执行前的逻辑
        Object result = joinPoint.proceed();
        // 方法执行后的逻辑
        return result;
    }
}

通知类型详解

执行顺序

对于同一个方法,各种通知的执行顺序:

正常执行:
@Around 前半部分 → @Before → 目标方法 → @AfterReturning → @After → @Around 后半部分

异常执行:
@Around 前半部分 → @Before → 目标方法 → @AfterThrowing → @After

获取方法信息

@Before("execution(* com.example.service.*.*(..))")
public void beforeAdvice(JoinPoint joinPoint) {
    // 获取方法签名
    Signature signature = joinPoint.getSignature();
    String methodName = signature.getName();
    String className = signature.getDeclaringTypeName();
    
    // 获取方法参数
    Object[] args = joinPoint.getArgs();
    
    // 获取目标对象
    Object target = joinPoint.getTarget();
    
    System.out.println("方法:" + className + "." + methodName);
    System.out.println("参数:" + Arrays.toString(args));
    System.out.println("目标对象:" + target.getClass().getSimpleName());
}

切点表达式

基本语法

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?
          name-pattern(param-pattern) throws-pattern?)

常用表达式

// 匹配所有方法
@Pointcut("execution(* *(..))")

// 匹配指定包下的所有方法
@Pointcut("execution(* com.example.service.*.*(..))")

// 匹配指定类的所有方法
@Pointcut("execution(* com.example.service.UserService.*(..))")

// 匹配指定方法名
@Pointcut("execution(* com.example.service.*.save*(..))")

// 匹配指定参数类型
@Pointcut("execution(* com.example.service.*.*(String, ..))")

// 匹配指定返回类型
@Pointcut("execution(public String com.example.service.*.*(..))")

// 匹配注解标记的方法
@Pointcut("@annotation(com.example.annotation.Log)")

// 匹配类级别注解
@Pointcut("@within(org.springframework.stereotype.Service)")

// 组合表达式
@Pointcut("execution(* com.example.service.*.*(..)) && @annotation(com.example.annotation.Log)")

实战示例

日志记录切面

@Aspect
@Component
public class LoggingAspect {
    
    private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
    
    @Pointcut("@annotation(com.example.annotation.Log)")
    public void logPointcut() {}
    
    @Around("logPointcut()")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        String className = joinPoint.getTarget().getClass().getSimpleName();
        
        long startTime = System.currentTimeMillis();
        
        try {
            logger.info("开始执行方法: {}.{}", className, methodName);
            Object result = joinPoint.proceed();
            logger.info("方法执行成功: {}.{}", className, methodName);
            return result;
        } catch (Exception e) {
            logger.error("方法执行失败: {}.{}, 异常: {}", className, methodName, e.getMessage());
            throw e;
        } finally {
            long endTime = System.currentTimeMillis();
            logger.info("方法执行耗时: {}ms", endTime - startTime);
        }
    }
}

事务管理切面

@Aspect
@Component
public class TransactionAspect {
    
    @Autowired
    private DataSourceTransactionManager transactionManager;
    
    @Around("@annotation(org.springframework.transaction.annotation.Transactional)")
    public Object handleTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
        TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
        
        try {
            Object result = joinPoint.proceed();
            transactionManager.commit(status);
            return result;
        } catch (Exception e) {
            transactionManager.rollback(status);
            throw e;
        }
    }
}

权限检查切面

@Aspect
@Component
public class SecurityAspect {
    
    @Before("@annotation(com.example.annotation.RequireRole)")
    public void checkPermission(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        RequireRole requireRole = signature.getMethod().getAnnotation(RequireRole.class);
        
        String[] roles = requireRole.value();
        String currentUserRole = getCurrentUserRole();
        
        boolean hasPermission = Arrays.asList(roles).contains(currentUserRole);
        
        if (!hasPermission) {
            throw new SecurityException("用户无权限访问该方法");
        }
    }
    
    private String getCurrentUserRole() {
        // 获取当前用户角色的逻辑
        return "USER";
    }
}

性能优化

1. 选择合适的代理方式

@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true) // 强制使用 CGLIB
public class AopConfig {
    
    @Bean
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        creator.setProxyTargetClass(true);
        return creator;
    }
}

2. 优化切点表达式

// 精确匹配,避免过度匹配
@Pointcut("execution(* com.example.service.UserService.save(..))")

// 使用注解方式,性能更好
@Pointcut("@annotation(com.example.annotation.Log)")

3. 异步处理

@Aspect
@Component
public class AsyncLoggingAspect {
    
    @Async
    @After("execution(* com.example.service.*.*(..))")
    public void asyncLogging(JoinPoint joinPoint) {
        // 异步记录日志,不影响主流程性能
        String methodName = joinPoint.getSignature().getName();
        System.out.println("异步记录日志: " + methodName);
    }
}

常见问题

问题1:AOP 不生效

现象: 切面代码没有执行 原因:

  • 没有启用 AOP 注解
  • 切点表达式错误
  • 目标对象没有被 Spring 管理

解决方案:

// 确保启用 AOP
@EnableAspectJAutoProxy

// 检查切点表达式
@Pointcut("execution(* com.example.service.*.*(..))")

// 确保目标类被 Spring 管理
@Service
public class UserService {
    // ...
}

问题2:切面执行顺序问题

现象: 多个切面执行顺序不正确 解决方案:

@Aspect
@Component
@Order(1)  // 数字越小优先级越高
public class SecurityAspect {
    // ...
}

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

问题3:内部方法调用切面失效

现象: 同一个类内部方法调用时切面不生效 原因: Spring AOP 基于代理,内部调用绕过了代理 解决方案:

@Service
public class UserService {
    
    @Autowired
    private UserService self;  // 注入自己的代理对象
    
    public void method1() {
        self.method2();  // 通过代理调用
    }
    
    @Log
    public void method2() {
        // ...
    }
}

问题4:切面中获取不到参数

现象: 在切面中无法获取方法参数 解决方案:

@Before("execution(* com.example.service.*.*(..)) && args(param)")
public void beforeAdvice(JoinPoint joinPoint, String param) {
    System.out.println("方法参数: " + param);
}

⚠️ 注意: 在生产环境中使用 AOP 时,要注意性能影响,避免在高频调用的方法上使用复杂的切面逻辑。

相关文章

前后章节导航

Spring基础系列

Spring进阶指南

MyBatis持久层框架

Spring MVC Web开发

SpringBoot现代化开发

总结

Spring AOP 是 Spring 框架的核心特性,通过面向切面编程实现了横切关注点的模块化管理。本指南涵盖了:

🎯 核心概念

  • AOP 基本概念:切面、连接点、切点、通知等
  • 代理模式:静态代理、JDK 动态代理、CGLIB 代理
  • Spring AOP:XML 配置、注解配置、AspectJ 集成

🛠️ 实现方式

  • JDK 动态代理:基于接口的代理实现
  • CGLIB 代理:基于继承的代理实现
  • Spring AOP:简化的企业级 AOP 解决方案

📚 实践应用

  • 日志记录:方法执行日志和性能监控
  • 事务管理:声明式事务控制
  • 权限检查:方法级别的安全控制