# spring-aop **Repository Path**: java-lesson/spring-aop ## Basic Information - **Project Name**: spring-aop - **Description**: No description available - **Primary Language**: Java - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-04-03 - **Last Updated**: 2026-04-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Spring AOP 入门示例项目 ## 项目介绍 这是一个**面向初学者**的 Spring AOP 入门演示项目,通过简单的用户管理示例展示如何使用 Spring AOP 实现方法拦截。 本项目**只包含一个日志切面**,使用最常见的 User 实体和 CRUD 操作,让初学者能够专注于理解 AOP 的核心概念,同时贴近实际工作场景。 ### 核心技术 - **Spring Framework 7.0.0**: 最新版本的 Spring - **Spring AOP**: Spring 提供的 AOP 实现(已包含在 spring-context 中) - **Spring Aspects**: 提供 AspectJ 注解支持 - **AspectJ Weaver**: 运行时织入器,用于动态织入切面逻辑 - **Maven**: 项目构建和依赖管理工具 ### 业务场景 项目模拟了一个简单的用户管理系统,包含基本的 CRUD 操作: 1. **创建用户** - 新增用户 2. **删除用户** - 删除用户 3. **异常处理** - 查询不存在的用户 ### AOP 核心概念 本项目演示了以下 AOP 核心概念: 1. **切面(Aspect)**: 横切关注点的模块化,如日志、事务、安全等 2. **连接点(JoinPoint)**: 程序执行过程中的某个点,如方法调用 3. **通知(Advice)**: 在特定连接点执行的动作 - 前置通知(@Before) - 返回通知(@AfterReturning) - 异常通知(@AfterThrowing) - 环绕通知(@Around) 4. **切入点(Pointcut)**: 匹配连接点的表达式,定义在哪些地方应用通知 5. **织入(Weaving)**: 将切面应用到目标对象的过程 ### AOP 在生产环境中的应用场景 | 场景 | 说明 | 典型应用 | 推荐实现方式 | 范围级别 | |------|------|----------|-------------|----------| | **日志记录** | 记录方法调用、参数、执行时间 | 操作审计、问题排查 | @Around + 自定义注解 | 包级别 / 类级别 | | **性能监控** | 监控方法执行耗时 | 性能瓶颈识别、系统优化 | @Around + ProceedingJoinPoint | 方法名模式 / 注解级别 | | **事务管理** | 管理数据库事务边界 | 数据一致性保证 | @Transactional 注解 | 具体方法 / 类级别 | | **权限验证** | 检查用户角色和权限 | 资源访问控制、安全保护 | @Before + 自定义注解 | 注解级别 / 方法名模式 | | **缓存处理** | 自动缓存查询结果 | 提升系统性能、减少数据库压力 | @Around + Spring Cache | 注解级别 | | **异常处理** | 统一异常捕获和处理 | 全局错误处理、友好提示 | @AfterThrowing + 全局异常处理器 | 包级别 / 类级别 | **范围级别说明**: - **包级别**:`execution(* com.example.service..*.*(..))` - 匹配整个包下的所有类和方法 - **类级别**:`execution(* com.example.service.UserService.*(..))` - 匹配特定类的所有方法 - **方法名模式**:`execution(* com.example.service.UserService.save*(..))` - 匹配符合命名模式的方法 - **具体方法**:`execution(* com.example.service.UserService.getUserById(Long))` - 精确匹配某个方法 - **注解级别**:`@annotation(com.example.LogExecutionTime)` - 匹配带有特定注解的方法 **实际项目示例**: ```java // 性能监控切面 @Aspect @Component public class PerformanceMonitorAspect { @Around("@annotation(LogExecutionTime)") public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable { long start = System.currentTimeMillis(); Object result = joinPoint.proceed(); long duration = System.currentTimeMillis() - start; System.out.println("方法执行耗时:" + duration + "ms"); return result; } } // 使用自定义注解 @LogExecutionTime public User getUserById(Long id) { // 业务逻辑 } ``` ## 项目结构 ``` spring-aop/ ├── pom.xml # Maven 配置文件 ├── src/main/java/com/example/aop/ │ ├── Application.java # 主程序入口 │ ├── config/ │ │ └── AppConfig.java # Spring 应用配置类 │ ├── service/ │ │ └── UserService.java # 用户服务类(业务逻辑) │ ├── model/ │ │ └── User.java # 用户模型 │ └── aspect/ │ └── LoggingAspect.java # 日志切面(唯一的一个切面) ├── .gitignore # Git 忽略文件配置 └── README.md # 项目说明文档 ``` ## 安装教程 ### 环境要求 - **JDK**: 17 或更高版本 - **Maven**: 3.6 或更高版本 ### 安装步骤 1. **克隆或下载项目** ```bash git clone cd spring-aop ``` 2. **使用 Maven 下载依赖** ```bash mvn clean install ``` Maven 会自动从中央仓库下载所有依赖 3. **编译项目** ```bash mvn compile ``` ## 使用说明 ### 方式一:使用 Maven 运行 ```bash mvn exec:java -Dexec.mainClass="com.example.aop.Application" ``` ### 方式二:打包后运行 ```bash # 1. 打包项目 mvn package # 2. 运行生成的 JAR 文件 java -jar target/spring-aop-demo-1.0-SNAPSHOT.jar ``` ### 方式三:在 IDE 中运行 1. 使用 IntelliJ IDEA 或 Eclipse 导入 Maven 项目 2. 找到 `Application.java` 文件 3. 右键运行 main 方法 ### 预期输出 运行程序后,你会看到类似以下的输出,展示了日志切面的执行效果: ``` ======================================== Spring AOP 日志切面演示程序 ======================================== 【测试 1】创建新用户 ────────────────────────────────────── [LOG] ===== 方法调用开始 ===== [LOG] 类名:com.example.aop.service.UserService [LOG] 方法名:createUser [LOG] 参数:[赵六,35] [LOG] ======================= === [UserService] 创建用户,姓名:赵六,年龄:35 === [UserService] 用户创建成功,ID: 4 [LOG] ✓ 方法 'createUser' 执行成功 [LOG] 返回值类型:User 创建成功:User{id=4, name='赵六', age=35} 【测试 2】删除用户 ID: 3 ────────────────────────────────────── [LOG] ===== 方法调用开始 ===== ... ``` ## 代码说明 ### 主要类说明 #### 1. **User** - 用户模型类 简单的 POJO,包含以下字段: - `id` - 用户 ID - `name` - 姓名 - `age` - 年龄 #### 2. **UserService** - 用户服务类 业务功能: - `getUserById()` - 根据 ID 查询用户(用于异常演示) - `createUser()` - 创建新用户 - `deleteUser()` - 删除用户 #### 3. **LoggingAspect** - 日志切面(唯一的一个) 功能: - 记录所有 UserService 方法的调用 - 记录方法参数和返回值 - 记录异常信息 使用场景:生产环境中的操作审计、问题排查 #### 4. **AppConfig** - 应用配置类 功能: - 启用 AspectJ 自动代理 - 扫描组件包路径 #### 5. **Application** - 主程序 演示了 3 个测试场景: 1. 创建新用户 2. 删除用户 ID: 3 3. 异常处理演示:查询不存在的用户 ### 关键代码示例 #### 切入点表达式 ```java // 匹配 UserService 的所有方法 @Pointcut("execution(* com.example.aop.service.UserService.*(..))") public void userServiceMethods() {} ``` #### 前置通知 ```java @Before("userServiceMethods()") public void beforeMethod(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); System.out.println("[LOG] ===== 方法调用开始 ====="); System.out.println("[LOG] 方法名:" + methodName); System.out.println("[LOG] 参数:" + Arrays.toString(args)); System.out.println("[LOG] ======================="); } ``` #### 返回通知 ```java @AfterReturning( pointcut = "userServiceMethods()", returning = "result" ) public void afterReturning(JoinPoint joinPoint, Object result) { String methodName = joinPoint.getSignature().getName(); System.out.println("[LOG] ✓ 方法 '" + methodName + "' 执行成功"); System.out.println("[LOG] 返回值类型:" + (result != null ? result.getClass().getSimpleName() : "null")); } ``` ## 参与贡献 1. Fork 本仓库 2. 新建 Feat_xxx 分支 3. 提交代码 4. 新建 Pull Request ## 学习资源 - [Spring 官方文档 - AOP](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop) - [AspectJ 编程指南](https://www.eclipse.org/aspectj/doc/released/progguide/index.html) - [Spring AOP vs AspectJ](https://www.baeldung.com/spring-aop-vs-aspectj) ## 常见问题 ### Q1: 为什么只有一个日志切面? A: 这是一个**面向初学者**的入门项目,目的是让学习者专注于理解 AOP 的核心概念,而不被多个切面的复杂交互所困扰。 当你掌握了基础的 AOP 用法后,可以扩展更多实用切面: - 性能监控切面 - 事务管理切面 - 权限验证切面 - 缓存处理切面 ### Q2: spring-aop 依赖是否可以移除? A: 这是一个关于 Maven 依赖管理的经典问题,有两种观点: **观点 1:应该显式声明(推荐用于生产项目)** ✅ 虽然 `spring-context` 已经包含了 `spring-aop`,但显式声明有以下好处: 1. **意图明确**:清楚表明项目明确依赖 spring-aop,而不是偶然使用 2. **抗变化性强**:即使未来 spring-context 移除该依赖,项目不受影响 3. **版本控制**:可以独立控制 spring-aop 的版本(如升级或降级) 4. **便于审计**:新人查看 pom.xml 就能理解完整依赖关系 **观点 2:依赖传递即可(适用于简单项目)** 只声明 `spring-context`,让 `spring-aop` 作为传递依赖自动引入,优点是简洁,但存在隐式依赖的风险。 **本项目的选择**: 我们采用 **观点 1**,显式声明了 `spring-aop` 依赖。这是生产项目的最佳实践,可以避免未来可能的依赖风险。 **实际验证方法**: ```bash # 查看项目完整依赖树 mvn dependency:tree # 你会看到 spring-aop 既通过 spring-context 传递引入 # 也通过显式声明直接引入 ``` ### Q3: spring-aspects 和 aspectjweaver 是什么关系? A: - **spring-aspects**: 提供 Spring 对 AspectJ 注解(如 `@Aspect`、`@Before` 等)的支持 - **aspectjweaver**: AspectJ 的织入器,负责在运行时将切面逻辑织入到目标对象中 - **使用 @Aspect 注解时,这两个都是必需的**,不能移除 ### Q4: 为什么我的切面没有生效? A: 检查以下几点: 1. 确保切面类添加了 `@Aspect` 和 `@Component` 注解 2. 确保配置类添加了 `@EnableAspectJAutoProxy` 3. 确保切入点表达式正确匹配目标方法 4. 确保通过 Spring 容器获取 Bean,而不是直接 new 对象 ### Q5: JDK 动态代理和 CGLIB 有什么区别? A: - **JDK 动态代理**: 只能代理实现了接口的类,基于接口代理 - **CGLIB**: 可以代理没有实现接口的类,基于子类代理 - Spring Boot 2.x+ 默认使用 CGLIB ### Q6: 如何在生产环境中使用 AOP? A: 参考上方的「AOP 在生产环境中的应用场景」表格,根据具体需求选择合适的切面类型。关键是: 1. **明确横切关注点**:识别哪些功能是跨多个模块的(如日志、安全) 2. **选择合适通知类型**: - 只需前置处理 → `@Before` - 需要返回值 → `@AfterReturning` - 需要异常处理 → `@AfterThrowing` - 需要完全控制 → `@Around`(功能最强,但性能开销最大) 3. **定义清晰的切入点**:使用 execution 或 annotation 方式 4. **避免过度使用**:AOP 虽强大,但滥用会增加代码理解难度