Spring IOC 和 AOP 完全详解:从入门到精通
Spring IOC 和 AOP 完全详解:从入门到精通
📝 博客简介:本文将深入浅出地讲解Spring框架的两大核心特性——IOC(控制反转)和AOP(面向切面编程)。适合Spring初学者和准备面试的同学阅读。
🎯 知识目标:理解IOC和AOP的概念、实现机制、应用场景,并掌握依赖注入和反射的原理。
📋 目录
一、Spring IOC(控制反转)详解
1.1 什么是IOC?
📖 概念解释
IOC(Inversion of Control,控制反转) 是一种设计原则,用于降低代码之间的耦合度。
💡 通俗理解:想象你是一个老板,以前你需要自己去找员工(new 对象),现在你把这个任务交给HR部门(Spring容器),你只需要告诉HR你需要什么样的员工(声明依赖),HR会帮你找到合适的人(注入对象)。这就是"控制反转"——对象创建的控制权从你的代码转移到了Spring容器。
🔑 核心思想
传统开发模式:
对象A需要对象B → 对象A自己创建对象B(new B()) IOC模式:
对象A需要对象B → 对象A声明需要B → Spring容器创建B并注入给A 📊 对比示例
传统方式(紧耦合):
/** * 传统方式:UserService 直接创建 UserDao * 缺点: * 1. 高耦合:UserService 直接依赖 UserDao 的具体实现 * 2. 难以测试:无法轻易替换 UserDao 的实现 * 3. 不灵活:如果要换一个 UserDao 实现,需要修改 UserService 代码 */publicclassUserService{// 直接在类内部创建依赖对象privateUserDao userDao =newUserDaoImpl();publicvoidaddUser(String username){// 使用 userDao 进行数据库操作 userDao.save(username);}}// UserDao 接口interfaceUserDao{voidsave(String username);}// UserDao 的具体实现classUserDaoImplimplementsUserDao{@Overridepublicvoidsave(String username){System.out.println("保存用户:"+ username);}}IOC方式(松耦合):
/** * IOC方式:UserService 不再自己创建 UserDao,而是通过依赖注入获得 * 优点: * 1. 低耦合:UserService 只依赖 UserDao 接口,不关心具体实现 * 2. 易于测试:可以轻松注入模拟对象(Mock)进行单元测试 * 3. 灵活性高:可以通过配置切换不同的 UserDao 实现,无需修改代码 */importorg.springframework.stereotype.Service;importorg.springframework.beans.factory.annotation.Autowired;@Service// 告诉Spring这是一个服务类,需要被Spring管理publicclassUserService{// 使用@Autowired注解,让Spring自动注入UserDao的实例// Spring会在容器中查找UserDao类型的Bean,并自动赋值给这个变量@AutowiredprivateUserDao userDao;publicvoidaddUser(String username){// 使用注入的 userDao 对象 userDao.save(username);}}// UserDao 接口定义interfaceUserDao{voidsave(String username);}// UserDao 的实现类importorg.springframework.stereotype.Repository;@Repository// 告诉Spring这是一个数据访问层组件,需要被Spring管理classUserDaoImplimplementsUserDao{@Overridepublicvoidsave(String username){System.out.println("保存用户到数据库:"+ username);}}1.2 IOC和AOP是通过什么机制来实现的?
🛠️ IOC的实现机制
Spring IOC容器主要通过以下技术实现:
- 反射(Reflection):动态创建对象
- 工厂模式:管理Bean的生命周期
- 注解或XML配置:描述Bean之间的依赖关系
📐 IOC容器工作流程
┌─────────────────────────────────────────────────────┐ │ Spring IOC容器 │ │ │ │ 1. 读取配置(XML/注解) │ │ ↓ │ │ 2. 解析Bean定义信息 │ │ ↓ │ │ 3. 使用反射创建Bean实例 │ │ ↓ │ │ 4. 进行依赖注入(属性赋值) │ │ ↓ │ │ 5. 初始化Bean(调用初始化方法) │ │ ↓ │ │ 6. 将Bean放入容器(可供使用) │ │ ↓ │ │ 7. 容器关闭时销毁Bean │ └─────────────────────────────────────────────────────┘ 🔧 AOP的实现机制
Spring AOP主要通过以下技术实现:
- 动态代理(Dynamic Proxy):
- JDK动态代理:针对实现了接口的类
- CGLIB代理:针对没有实现接口的类
- 字节码增强:在运行时修改或生成字节码
📊 动态代理原理图
原始对象(Target) ↓ Spring AOP 检测到需要增强 ↓ 创建代理对象(Proxy) ↓ ┌─────────────────────────────────┐ │ 代理对象 │ │ ┌──────────────────────────┐ │ │ │ 前置通知(Before) │ │ │ └──────────────────────────┘ │ │ ↓ │ │ ┌──────────────────────────┐ │ │ │ 调用原始方法 │ │ │ └──────────────────────────┘ │ │ ↓ │ │ ┌──────────────────────────┐ │ │ │ 后置通知(After) │ │ │ └──────────────────────────┘ │ └─────────────────────────────────┘ 1.3 怎么理解Spring IOC?
🎯 核心理解
Spring IOC的本质是一个对象工厂,它负责:
- 创建对象:根据配置信息创建Bean实例
- 管理对象:管理对象的生命周期(从创建到销毁)
- 装配对象:处理对象之间的依赖关系
📦 IOC容器的两种类型
| 容器类型 | 说明 | 应用场景 |
|---|---|---|
| BeanFactory | 基础容器,延迟加载Bean | 资源受限的环境 |
| ApplicationContext | 高级容器,启动时加载所有Bean | 企业级应用(推荐) |
💡 生活类比:BeanFactory像外卖平台,你点餐(调用getBean)时才开始做菜(创建Bean);ApplicationContext像自助餐厅,所有菜品(Bean)在开门时就已经准备好了。
1.4 依赖注入(DI)详解
📖 什么是依赖注入?
依赖注入(Dependency Injection,DI) 是IOC的一种实现方式,它是指:由Spring容器在运行期间,动态地将依赖关系注入到对象中。
💡 通俗理解:你开了一家餐厅(类),需要厨师(依赖对象)。传统方式是你自己去招聘(new对象),而依赖注入就是HR(Spring容器)直接把合格的厨师送到你的餐厅。
🎯 依赖注入的三种方式
1️⃣ 构造器注入(Constructor Injection)
特点:通过构造方法注入依赖,适合必需的、不可变的依赖。
/** * 构造器注入示例 * 优点: * 1. 依赖关系明确,必须在对象创建时提供 * 2. 注入的对象可以声明为final,保证不可变性 * 3. 便于编写单元测试 * * 适用场景:强制依赖、不可变对象 */importorg.springframework.stereotype.Service;importorg.springframework.beans.factory.annotation.Autowired;@ServicepublicclassOrderService{// 使用final确保依赖不可变privatefinalUserService userService;privatefinalProductService productService;/** * 构造器注入 * @Autowired 可以省略(Spring 4.3+,只有一个构造器时) * * @param userService 用户服务 * @param productService 商品服务 */@AutowiredpublicOrderService(UserService userService,ProductService productService){// Spring会自动传入这两个参数this.userService = userService;this.productService = productService;System.out.println("OrderService通过构造器注入创建");}/** * 创建订单的业务方法 */publicvoidcreateOrder(String username,String productName){// 使用注入的依赖 userService.addUser(username);System.out.println("订单创建成功:用户="+ username +", 商品="+ productName);}}2️⃣ Setter方法注入(Setter Injection)
特点:通过setter方法注入依赖,适合可选的、可变的依赖。
/** * Setter注入示例 * 优点: * 1. 灵活性高,可以在对象创建后修改依赖 * 2. 可以注入可选依赖 * * 缺点: * 1. 可能造成对象处于不完整状态 * 2. 依赖关系不够明确 * * 适用场景:可选依赖、需要重新配置的依赖 */importorg.springframework.stereotype.Service;importorg.springframework.beans.factory.annotation.Autowired;@ServicepublicclassEmailService{privateMailSender mailSender;// 非final,可以修改/** * Setter方法注入 * Spring会自动调用这个方法,传入MailSender实例 * * @param mailSender 邮件发送器 */@AutowiredpublicvoidsetMailSender(MailSender mailSender){this.mailSender = mailSender;System.out.println("通过Setter方法注入MailSender");}/** * 发送邮件 */publicvoidsendEmail(Stringto,String content){if(mailSender !=null){ mailSender.send(to, content);}else{System.out.println("MailSender未注入,无法发送邮件");}}}3️⃣ 字段注入(Field Injection)
特点:直接在字段上使用@Autowired注解,最简洁但不推荐。
/** * 字段注入示例 * 优点: * 1. 代码最简洁 * 2. 使用方便 * * 缺点: * 1. 难以进行单元测试(无法轻松注入Mock对象) * 2. 违反了依赖注入的原则(依赖关系不明确) * 3. 无法声明为final * 4. IDE会给出警告 * * 适用场景:快速开发、原型项目(生产环境不推荐) */importorg.springframework.stereotype.Service;importorg.springframework.beans.factory.annotation.Autowired;@ServicepublicclassPaymentService{// 直接在字段上注入,Spring通过反射设置值@AutowiredprivateUserService userService;@AutowiredprivateOrderService orderService;/** * 处理支付 */publicvoidprocessPayment(String username,double amount){System.out.println("处理支付:用户="+ username +", 金额="+ amount);// 使用注入的依赖 userService.addUser(username);}}🎨 三种注入方式对比
| 注入方式 | 语法复杂度 | 测试友好度 | 依赖明确性 | 推荐度 | 适用场景 |
|---|---|---|---|---|---|
| 构造器注入 | 中等 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 必需依赖、生产环境 |
| Setter注入 | 简单 | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | 可选依赖 |
| 字段注入 | 最简单 | ⭐⭐ | ⭐⭐ | ⭐⭐ | 快速开发、原型 |
🏆 最佳实践:优先使用构造器注入,它是Spring官方推荐的方式。
二、Spring AOP(面向切面编程)详解
2.1 什么是AOP?
📖 概念解释
AOP(Aspect Oriented Programming,面向切面编程) 是一种编程范式,它允许程序员模块化横切关注点(Cross-cutting Concerns)。
💡 通俗理解:想象你在开发一个电商系统,每个功能(登录、下单、支付)都需要记录日志、检查权限、统计性能。如果在每个方法里都写这些代码,会非常重复。AOP就像一个"管家",它可以在方法执行前后自动帮你做这些事,而你只需要专注于业务逻辑。
🎯 核心概念
| 概念 | 英文 | 说明 | 举例 |
|---|---|---|---|
| 切面 | Aspect | 横切关注点的模块化 | 日志切面、事务切面 |
| 连接点 | Join Point | 程序执行的某个点 | 方法调用、异常抛出 |
| 切点 | Pointcut | 匹配连接点的表达式 | 所有Service层的方法 |
| 通知 | Advice | 在切点执行的代码 | 前置通知、后置通知 |
| 目标对象 | Target | 被增强的对象 | UserService实例 |
| 代理对象 | Proxy | AOP创建的对象 | UserService的代理 |
| 织入 | Weaving | 将切面应用到目标的过程 | Spring在运行时织入 |
📊 AOP概念图解
切面(Aspect) ┌─────────────────────┐ │ @Aspect │ │ LoggingAspect │ │ │ │ 切点(Pointcut) │ │ ↓ │ │ execution(* com.example..*(..)) │ │ │ │ 通知(Advice) │ │ ↓ │ │ @Before │ │ @After │ │ @Around │ └─────────────────────┘ ↓ 织入(Weaving) ↓ ┌─────────────────────┐ │ 目标对象(Target) │ │ UserService │ └─────────────────────┘ ↓ ┌─────────────────────┐ │ 代理对象(Proxy) │ │ UserServiceProxy │ └─────────────────────┘ 🔍 AOP的五种通知类型
/** * AOP通知类型完整示例 * 演示五种通知类型的执行时机和用法 */importorg.aspectj.lang.annotation.*;importorg.aspectj.lang.JoinPoint;importorg.aspectj.lang.ProceedingJoinPoint;importorg.springframework.stereotype.Component;/** * 日志切面:演示各种通知类型 */@Aspect// 标记这是一个切面类@Component// 将切面类注册为Spring BeanpublicclassLoggingAspect{/** * 1. 前置通知(Before Advice) * 在目标方法执行之前执行 * * 使用场景:参数验证、权限检查、日志记录 * * @param joinPoint 连接点,包含方法信息 */@Before("execution(* com.example.service.*.*(..))")publicvoidbeforeAdvice(JoinPoint joinPoint){// 获取方法名String methodName = joinPoint.getSignature().getName();// 获取参数Object[] args = joinPoint.getArgs();System.out.println("=== 前置通知 ===");System.out.println("即将执行方法:"+ methodName);System.out.println("方法参数:"+java.util.Arrays.toString(args));}/** * 2. 后置通知(After Returning Advice) * 在目标方法正常返回后执行(不包括异常情况) * * 使用场景:记录返回值、数据处理、缓存更新 * * @param joinPoint 连接点 * @param result 方法返回值 */@AfterReturning( pointcut ="execution(* com.example.service.*.*(..))", returning ="result"// 指定返回值参数名)publicvoidafterReturningAdvice(JoinPoint joinPoint,Object result){String methodName = joinPoint.getSignature().getName();System.out.println("=== 后置通知 ===");System.out.println("方法执行成功:"+ methodName);System.out.println("返回值:"+ result);}/** * 3. 异常通知(After Throwing Advice) * 在目标方法抛出异常后执行 * * 使用场景:异常日志记录、异常转换、告警通知 * * @param joinPoint 连接点 * @param exception 抛出的异常 */@AfterThrowing( pointcut ="execution(* com.example.service.*.*(..))", throwing ="exception"// 指定异常参数名)publicvoidafterThrowingAdvice(JoinPoint joinPoint,Exception exception){String methodName = joinPoint.getSignature().getName();System.out.println("=== 异常通知 ===");System.out.println("方法执行异常:"+ methodName);System.out.println("异常信息:"+ exception.getMessage());}/** * 4. 最终通知(After Advice) * 在目标方法执行之后执行(无论是否抛出异常) * 类似于finally块 * * 使用场景:资源释放、清理工作 * * @param joinPoint 连接点 */@After("execution(* com.example.service.*.*(..))")publicvoidafterAdvice(JoinPoint joinPoint){String methodName = joinPoint.getSignature().getName();System.out.println("=== 最终通知 ===");System.out.println("方法执行完毕:"+ methodName);}/** * 5. 环绕通知(Around Advice) * 包围目标方法,可以控制方法的执行 * 最强大的通知类型 * * 使用场景:性能统计、事务管理、缓存处理 * * @param proceedingJoinPoint 可执行的连接点 * @return 方法返回值 * @throws Throwable 方法可能抛出的异常 */@Around("execution(* com.example.service.*.*(..))")publicObjectaroundAdvice(ProceedingJoinPoint proceedingJoinPoint)throwsThrowable{String methodName = proceedingJoinPoint.getSignature().getName();System.out.println("=== 环绕通知开始 ===");System.out.println("准备执行方法:"+ methodName);// 记录开始时间long startTime =System.currentTimeMillis();Object result =null;try{// 执行目标方法(这是关键!) result = proceedingJoinPoint.proceed();// 目标方法执行成功System.out.println("方法执行成功,返回值:"+ result);}catch(Throwable throwable){// 目标方法执行失败System.out.println("方法执行失败:"+ throwable.getMessage());throw throwable;// 重新抛出异常}finally{// 计算执行时间long endTime =System.currentTimeMillis();long executionTime = endTime - startTime;System.out.println("方法执行耗时:"+ executionTime +"ms");System.out.println("=== 环绕通知结束 ===");}// 返回结果(可以修改返回值)return result;}}📈 通知执行顺序
正常执行流程: @Around 开始 ↓ @Before ↓ 目标方法执行 ↓ @AfterReturning ↓ @After ↓ @Around 结束 异常执行流程: @Around 开始 ↓ @Before ↓ 目标方法执行(抛出异常) ↓ @AfterThrowing ↓ @After ↓ @Around 异常处理 2.2 Spring AOP主要想解决什么问题?
🎯 解决的核心问题
Spring AOP主要解决代码重复和横切关注点分离的问题。
📊 横切关注点示例
业务逻辑层 ┌─────────────────────────┐ │ │ │ UserService │ ← 日志、事务、权限 │ OrderService │ ← 日志、事务、权限 │ ProductService │ ← 日志、事务、权限 │ PaymentService │ ← 日志、事务、权限 │ │ └─────────────────────────┘ 横切关注点: - 日志记录 - 事务管理 - 权限检查 - 性能监控 ❌ 没有AOP的代码(问题代码)
/** * 没有AOP的代码:每个方法都要写重复的横切逻辑 * 问题: * 1. 代码重复 * 2. 业务逻辑和横切逻辑混在一起 * 3. 难以维护 * 4. 修改横切逻辑需要改动所有方法 */publicclassUserServiceWithoutAOP{publicvoidaddUser(String username){// ===== 横切逻辑开始 =====// 1. 日志记录System.out.println("[日志] 开始执行addUser方法,参数:"+ username);long startTime =System.currentTimeMillis();try{// 2. 权限检查if(!checkPermission()){System.out.println("[权限] 无权限执行此操作");return;}// 3. 事务开启beginTransaction();// ===== 业务逻辑开始 =====System.out.println("执行添加用户的业务逻辑:"+ username);// 实际的业务代码...// ===== 业务逻辑结束 =====// 4. 事务提交commitTransaction();}catch(Exception e){// 5. 事务回滚rollbackTransaction();System.out.println("[异常] 方法执行失败:"+ e.getMessage());}finally{// 6. 性能统计long endTime =System.currentTimeMillis();System.out.println("[性能] 方法执行耗时:"+(endTime - startTime)+"ms");}// ===== 横切逻辑结束 =====}publicvoiddeleteUser(String username){// 同样的横切逻辑又要写一遍...System.out.println("[日志] 开始执行deleteUser方法,参数:"+ username);// ... 重复代码 ...}// 辅助方法privatebooleancheckPermission(){returntrue;}privatevoidbeginTransaction(){}privatevoidcommitTransaction(){}privatevoidrollbackTransaction(){}}✅ 使用AOP的代码(优雅代码)
/** * 使用AOP的代码:业务逻辑和横切逻辑完全分离 * 优点: * 1. 代码简洁,只关注业务逻辑 * 2. 横切逻辑集中管理 * 3. 易于维护和扩展 * 4. 修改横切逻辑只需修改切面类 */importorg.springframework.stereotype.Service;@ServicepublicclassUserServiceWithAOP{/** * 添加用户 * 注意:这个方法只包含业务逻辑 * 日志、权限、事务等横切逻辑由AOP切面自动处理 */publicvoidaddUser(String username){// 纯粹的业务逻辑,简洁明了System.out.println("执行添加用户的业务逻辑:"+ username);}/** * 删除用户 */publicvoiddeleteUser(String username){// 纯粹的业务逻辑System.out.println("执行删除用户的业务逻辑:"+ username);}}三、反射机制详解
3.1 什么是反射?
📖 概念解释
反射(Reflection) 是Java提供的一种能力,允许程序在运行时检查和操作类、方法、字段等信息,即使在编译时不知道它们的名称。
💡 通俗理解:想象你是一个侦探,反射就像是一个X光机,可以让你在程序运行时"透视"一个对象,看到它的内部结构(字段)、能力(方法)、身份(类型),甚至可以修改它们。
🔑 反射的核心功能
- 获取类信息:获取类的名称、父类、接口、修饰符等
- 创建对象:动态创建类的实例
- 访问字段:获取和修改对象的字段值(包括私有字段)
- 调用方法:动态调用对象的方法(包括私有方法)
- 操作数组:动态创建和操作数组
📊 反射API核心类
| 类名 | 说明 | 主要方法 |
|---|---|---|
| Class | 表示类的信息 | forName(), newInstance(), getName() |
| Constructor | 表示构造方法 | newInstance(), getParameterTypes() |
| Method | 表示方法 | invoke(), getName(), getReturnType() |
| Field | 表示字段 | get(), set(), setAccessible() |
🔍 反射完整示例
/** * 反射机制完整示例 * 演示反射的各种用法 */importjava.lang.reflect.*;/** * 用户类(作为反射操作的目标) */classUser{// 私有字段privateString username;privateint age;// 无参构造器publicUser(){System.out.println("无参构造器被调用");}// 有参构造器publicUser(String username,int age){this.username = username;this.age = age;System.out.println("有参构造器被调用:"+ username +", "+ age);}// 公共方法publicvoidsayHello(){System.out.println("Hello, I'm "+ username);}// 私有方法privatevoidsecretMethod(){System.out.println("这是一个私有方法");}// Getter和SetterpublicStringgetUsername(){return username;}publicvoidsetUsername(String username){this.username = username;}publicintgetAge(){return age;}publicvoidsetAge(int age){this.age = age;}@OverridepublicStringtoString(){return"User{username='"+ username +"', age="+ age +"}";}}/** * 反射示例类 */publicclassReflectionDemo{publicstaticvoidmain(String[] args)throwsException{System.out.println("=== 反射机制演示 ===\n");// 1. 获取Class对象的三种方式demonstrateGetClass();// 2. 创建对象demonstrateCreateInstance();// 3. 访问和修改字段demonstrateFieldAccess();// 4. 调用方法demonstrateMethodInvocation();// 5. 获取类信息demonstrateClassInfo();}/** * 演示获取Class对象的三种方式 */privatestaticvoiddemonstrateGetClass()throwsException{System.out.println("=== 1. 获取Class对象 ===");// 方式1:通过类名.classClass<?> clazz1 =User.class;System.out.println("方式1(类名.class):"+ clazz1.getName());// 方式2:通过对象.getClass()User user =newUser();Class<?> clazz2 = user.getClass();System.out.println("方式2(对象.getClass()):"+ clazz2.getName());// 方式3:通过Class.forName()(最常用)Class<?> clazz3 =Class.forName("User");System.out.println("方式3(Class.forName()):"+ clazz3.getName());System.out.println();}/** * 演示创建对象 */privatestaticvoiddemonstrateCreateInstance()throwsException{System.out.println("=== 2. 创建对象 ===");Class<?> clazz =User.class;// 方式1:使用无参构造器创建对象System.out.println("方式1:使用无参构造器");User user1 =(User) clazz.getDeclaredConstructor().newInstance();// 方式2:使用有参构造器创建对象System.out.println("\n方式2:使用有参构造器");Constructor<?> constructor = clazz.getConstructor(String.class,int.class);User user2 =(User) constructor.newInstance("张三",25);System.out.println("创建的对象:"+ user2);System.out.println();}/** * 演示访问和修改字段 */privatestaticvoiddemonstrateFieldAccess()throwsException{System.out.println("=== 3. 访问和修改字段 ===");User user =newUser("李四",30);Class<?> clazz = user.getClass();// 获取私有字段Field usernameField = clazz.getDeclaredField("username");Field ageField = clazz.getDeclaredField("age");// 设置可访问(绕过private限制) usernameField.setAccessible(true); ageField.setAccessible(true);// 读取字段值System.out.println("原始username:"+ usernameField.get(user));System.out.println("原始age:"+ ageField.get(user));// 修改字段值 usernameField.set(user,"王五"); ageField.set(user,35);System.out.println("修改后的对象:"+ user);System.out.println();}/** * 演示调用方法 */privatestaticvoiddemonstrateMethodInvocation()throwsException{System.out.println("=== 4. 调用方法 ===");User user =newUser("赵六",28);Class<?> clazz = user.getClass();// 调用公共方法System.out.println("调用公共方法:");Method sayHelloMethod = clazz.getMethod("sayHello"); sayHelloMethod.invoke(user);// 调用私有方法System.out.println("\n调用私有方法:");Method secretMethod = clazz.getDeclaredMethod("secretMethod"); secretMethod.setAccessible(true);// 绕过private限制 secretMethod.invoke(user);System.out.println();}/** * 演示获取类信息 */privatestaticvoiddemonstrateClassInfo()throwsException{System.out.println("=== 5. 获取类信息 ===");Class<?> clazz =User.class;// 获取类名System.out.println("类名:"+ clazz.getName());System.out.println("简单类名:"+ clazz.getSimpleName());// 获取所有公共字段System.out.println("\n公共字段:");Field[] publicFields = clazz.getFields();for(Field field : publicFields){System.out.println(" "+ field.getName());}// 获取所有字段(包括私有)System.out.println("\n所有字段:");Field[] allFields = clazz.getDeclaredFields();for(Field field : allFields){System.out.println(" "+Modifier.toString(field.getModifiers())+" "+ field.getType().getSimpleName()+" "+ field.getName());}// 获取所有方法System.out.println("\n所有方法:");Method[] methods = clazz.getDeclaredMethods();for(Method method : methods){System.out.println(" "+ method.getName()+"()");}System.out.println();}}3.2 反射的使用场景
🎯 主要使用场景
| 场景 | 说明 | 示例 |
|---|---|---|
| 框架开发 | Spring IOC容器使用反射创建Bean | Spring, Hibernate |
| 动态代理 | AOP使用反射实现动态代理 | Spring AOP, MyBatis |
| 注解处理 | 读取和处理注解 | JUnit, Spring |
| 序列化/反序列化 | JSON与对象互转 | Jackson, Gson, Fastjson |
| ORM框架 | 数据库记录映射为对象 | Hibernate, MyBatis |
| 插件系统 | 动态加载和执行插件 | Eclipse插件, IDEA插件 |
| 工具类 | 通用的工具方法 | BeanUtils, PropertyUtils |
⚠️ 反射的优缺点
优点:
- ✅ 增加程序的灵活性,可以在运行时动态操作
- ✅ 降低耦合度,无需在编译时确定具体类型
- ✅ 支持动态创建对象和调用方法
缺点:
- ❌ 性能开销大,比直接调用慢10-100倍
- ❌ 破坏封装性,可以访问私有成员
- ❌ 代码复杂度高,难以理解和维护
五、常见面试问题汇总
💼 IOC相关面试题
Q1: 什么是IOC?IOC有什么好处?
答案:
IOC(控制反转)是一种设计原则,将对象创建和依赖关系的管理从应用程序代码转移到容器中。
好处:
- 降低耦合度:对象之间不直接依赖,而是通过接口依赖
- 提高可测试性:可以轻松注入Mock对象进行单元测试
- 提高可维护性:修改依赖关系只需修改配置,无需修改代码
- 统一管理:所有对象的生命周期由容器统一管理
Q2: IOC和DI的区别是什么?
答案:
- IOC(控制反转):是一种设计思想,指将对象创建的控制权交给容器
- DI(依赖注入):是IOC的一种实现方式,指容器在运行时动态地将依赖注入到对象中
关系: IOC是目标,DI是实现IOC的手段。
Q3: Spring IOC容器的初始化过程是怎样的?
答案:
- 加载配置:读取XML配置文件或扫描注解
- 解析配置:将配置信息解析为BeanDefinition对象
- 注册BeanDefinition:将BeanDefinition注册到容器中
- 实例化Bean:使用反射创建Bean实例
- 属性赋值:进行依赖注入,为Bean的属性赋值
- 初始化Bean:调用初始化方法(如@PostConstruct、InitializingBean)
- 使用Bean:Bean可以被应用程序使用
- 销毁Bean:容器关闭时调用销毁方法(如@PreDestroy、DisposableBean)
Q4: @Autowired和@Resource的区别?
答案:
| 对比项 | @Autowired | @Resource |
|---|---|---|
| 来源 | Spring框架 | Java标准(JSR-250) |
| 装配方式 | 默认按类型(byType) | 默认按名称(byName) |
| required属性 | 支持 | 不支持 |
| 适用范围 | 字段、构造器、方法 | 字段、方法 |
| 推荐 | Spring项目推荐 | 跨框架时使用 |
示例:
// @Autowired:按类型注入@AutowiredprivateUserService userService;// @Resource:按名称注入@Resource(name ="userServiceImpl")privateUserService userService;Q5: Spring Bean的作用域有哪些?
答案:
| 作用域 | 说明 | 使用场景 |
|---|---|---|
| singleton | 单例(默认) | 无状态的Bean(Service、Dao) |
| prototype | 每次获取创建新实例 | 有状态的Bean |
| request | 每次HTTP请求创建 | Web应用(请求级数据) |
| session | 每个Session创建 | Web应用(用户会话数据) |
| application | 整个ServletContext创建 | Web应用(全局数据) |
| websocket | 每个WebSocket创建 | WebSocket应用 |
示例:
@Service@Scope("prototype")// 设置为原型作用域publicclassUserService{// ...}💼 AOP相关面试题
Q6: 什么是AOP?AOP的应用场景有哪些?
答案:
AOP(面向切面编程)是一种编程范式,用于将横切关注点(如日志、事务、权限)从业务逻辑中分离出来。
应用场景:
- 日志记录:记录方法调用信息
- 事务管理:自动开启、提交、回滚事务
- 权限验证:检查用户是否有权限执行操作
- 性能监控:统计方法执行时间
- 异常处理:统一处理异常
- 缓存处理:自动缓存方法返回值
Q7: AOP的实现原理是什么?JDK动态代理和CGLIB代理的区别?
答案:
实现原理: Spring AOP使用动态代理技术,在运行时为目标对象创建代理对象,在代理对象中织入横切逻辑。
JDK动态代理 vs CGLIB代理:
| 对比项 | JDK动态代理 | CGLIB代理 |
|---|---|---|
| 原理 | 基于接口,实现InvocationHandler | 基于继承,生成子类 |
| 要求 | 目标类必须实现接口 | 目标类不能是final |
| 性能 | 略慢 | 略快 |
| Spring选择 | 实现了接口时默认使用 | 未实现接口时使用 |
示例:
// JDK动态代理:目标类实现接口publicinterfaceUserService{voidaddUser(String username);}@ServicepublicclassUserServiceImplimplementsUserService{@OverridepublicvoidaddUser(String username){System.out.println("添加用户:"+ username);}}// CGLIB代理:目标类没有实现接口@ServicepublicclassProductService{publicvoidaddProduct(String productName){System.out.println("添加商品:"+ productName);}}Q8: AOP的五种通知类型分别在什么时候执行?
答案:
| 通知类型 | 注解 | 执行时机 | 使用场景 |
|---|---|---|---|
| 前置通知 | @Before | 方法执行前 | 参数验证、权限检查 |
| 后置通知 | @AfterReturning | 方法正常返回后 | 记录返回值、缓存更新 |
| 异常通知 | @AfterThrowing | 方法抛出异常后 | 异常日志、告警通知 |
| 最终通知 | @After | 方法执行后(无论是否异常) | 资源释放、清理工作 |
| 环绕通知 | @Around | 包围整个方法 | 性能统计、事务管理 |
执行顺序(正常情况):
@Around 开始 → @Before → 目标方法 → @AfterReturning → @After → @Around 结束 Q9: 切点表达式怎么写?
答案:
语法格式:
execution(修饰符? 返回类型 包名.类名.方法名(参数) 异常?) 常用通配符:
*:匹配任意字符..:匹配任意层级的包或任意个参数+:匹配指定类及其子类
示例:
// 1. 匹配所有public方法execution(public**(..))// 2. 匹配service包下所有类的所有方法execution(* com.example.service.*.*(..))// 3. 匹配service包及子包下所有类的所有方法execution(* com.example.service..*.*(..))// 4. 匹配UserService类的所有方法execution(*com.example.service.UserService.*(..))// 5. 匹配add开头的方法execution(* com.example.service.*Service.add*(..))// 6. 匹配只有一个String参数的方法execution(* com.example.service.*Service.*(String))// 7. 匹配第一个参数为String的方法execution(* com.example.service.*Service.*(String,..))💼 反射相关面试题
Q10: 什么是反射?反射有什么应用?
答案:
反射是Java提供的一种能力,允许程序在运行时检查和操作类、方法、字段等信息。
应用:
- Spring框架:IOC容器使用反射创建Bean
- ORM框架:Hibernate、MyBatis使用反射映射数据
- 动态代理:AOP使用反射实现
- 序列化:JSON库使用反射转换对象
- 单元测试:Junit使用反射执行测试方法
- 注解处理:框架使用反射读取注解
Q11: 反射的性能问题如何优化?
答案:
性能问题原因:
- 反射需要查找类信息,比直接调用慢
- 需要进行权限检查
- 涉及到对象包装/拆包
优化方案:
- 缓存反射结果:缓存Class、Method、Field对象
- 关闭安全检查:
method.setAccessible(true) - 使用MethodHandle:Java 7+提供的更高效的方式
- 减少反射使用:能直接调用就不用反射
- 使用反射工具:Apache Commons BeanUtils
示例:
// 优化前:每次都获取Method对象for(int i =0; i <10000; i++){Method method = clazz.getMethod("sayHello"); method.invoke(user);}// 优化后:缓存Method对象Method method = clazz.getMethod("sayHello"); method.setAccessible(true);// 关闭安全检查for(int i =0; i <10000; i++){ method.invoke(user);}Q12: 如何获取Class对象?
答案:
有三种方式获取Class对象:
// 方式1:通过类名.class(编译时确定)Class<?> clazz1 =User.class;// 方式2:通过对象.getClass()(运行时获取)User user =newUser();Class<?> clazz2 = user.getClass();// 方式3:通过Class.forName()(最灵活)Class<?> clazz3 =Class.forName("com.example.User");选择建议:
- 编译时已知类型:使用
类名.class - 已有对象实例:使用
对象.getClass() - 类名在配置文件中:使用
Class.forName()
🎓 总结
核心要点回顾
- IOC(控制反转)
- 将对象创建的控制权交给Spring容器
- 通过依赖注入实现
- 优先使用构造器注入
- AOP(面向切面编程)
- 用于模块化横切关注点
- 通过动态代理实现
- 五种通知类型,环绕通知最强大
- 反射(Reflection)
- 运行时操作类和对象
- Spring框架的基础技术
- 注意性能问题,合理使用缓存
学习建议
- 📚 理论学习:先理解概念,再看源码实现
- 💻 实践练习:动手写代码,加深理解
- 🔍 源码阅读:阅读Spring源码,学习设计思想
- 📝 总结归纳:整理笔记,形成知识体系
🎉 恭喜你! 你已经完成了Spring IOC和AOP的学习。希望这篇博客能帮助你更好地理解和使用Spring框架。继续加油!