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 代码
*/
public class UserService {
// 直接在类内部创建依赖对象
private UserDao userDao = new UserDaoImpl();
public void addUser(String username) {
// 使用 userDao 进行数据库操作
userDao.save(username);
}
}
// UserDao 接口
interface UserDao {
void save(String username);
}
// UserDao 的具体实现
class UserDaoImpl implements UserDao {
@Override
public void save(String username) {
System.out.println("保存用户:" + username);
}
}IOC方式(松耦合):
/**
* IOC方式:UserService 不再自己创建 UserDao,而是通过依赖注入获得
* 优点:
* 1. 低耦合:UserService 只依赖 UserDao 接口,不关心具体实现
* 2. 易于测试:可以轻松注入模拟对象(Mock)进行单元测试
* 3. 灵活性高:可以通过配置切换不同的 UserDao 实现,无需修改代码
*/
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
@Service // 告诉Spring这是一个服务类,需要被Spring管理
public class UserService {
// 使用@Autowired注解,让Spring自动注入UserDao的实例
// Spring会在容器中查找UserDao类型的Bean,并自动赋值给这个变量
@Autowired
private UserDao userDao;
public void addUser(String username) {
// 使用注入的 userDao 对象
userDao.save(username);
}
}
// UserDao 接口定义
interface UserDao {
void save(String username);
}
// UserDao 的实现类
import org.springframework.stereotype.Repository;
@Repository // 告诉Spring这是一个数据访问层组件,需要被Spring管理
class UserDaoImpl implements UserDao {
@Override
public void save(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. 便于编写单元测试
*
* 适用场景:强制依赖、不可变对象
*/
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
@Service
public class OrderService {
// 使用final确保依赖不可变
private final UserService userService;
private final ProductService productService;
/**
* 构造器注入
* @Autowired 可以省略(Spring 4.3+,只有一个构造器时)
*
* @param userService 用户服务
* @param productService 商品服务
*/
@Autowired
public OrderService(UserService userService, ProductService productService) {
// Spring会自动传入这两个参数
this.userService = userService;
this.productService = productService;
System.out.println("OrderService通过构造器注入创建");
}
/**
* 创建订单的业务方法
*/
public void createOrder(String username, String productName) {
// 使用注入的依赖
userService.addUser(username);
System.out.println("订单创建成功:用户=" + username + ", 商品=" + productName);
}
}2️⃣ Setter方法注入(Setter Injection)
特点:通过setter方法注入依赖,适合可选的、可变的依赖。
/**
* Setter注入示例
* 优点:
* 1. 灵活性高,可以在对象创建后修改依赖
* 2. 可以注入可选依赖
*
* 缺点:
* 1. 可能造成对象处于不完整状态
* 2. 依赖关系不够明确
*
* 适用场景:可选依赖、需要重新配置的依赖
*/
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
@Service
public class EmailService {
private MailSender mailSender; // 非final,可以修改
/**
* Setter方法注入
* Spring会自动调用这个方法,传入MailSender实例
*
* @param mailSender 邮件发送器
*/
@Autowired
public void setMailSender(MailSender mailSender) {
this.mailSender = mailSender;
System.out.println("通过Setter方法注入MailSender");
}
/**
* 发送邮件
*/
public void sendEmail(String to, 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会给出警告
*
* 适用场景:快速开发、原型项目(生产环境不推荐)
*/
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
@Service
public class PaymentService {
// 直接在字段上注入,Spring通过反射设置值
@Autowired
private UserService userService;
@Autowired
private OrderService orderService;
/**
* 处理支付
*/
public void processPayment(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通知类型完整示例
* 演示五种通知类型的执行时机和用法
*/
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;
/**
* 日志切面:演示各种通知类型
*/
@Aspect // 标记这是一个切面类
@Component // 将切面类注册为Spring Bean
public class LoggingAspect {
/**
* 1. 前置通知(Before Advice)
* 在目标方法执行之前执行
*
* 使用场景:参数验证、权限检查、日志记录
*
* @param joinPoint 连接点,包含方法信息
*/
@Before("execution(* com.example.service.*.*(..))")
public void beforeAdvice(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" // 指定返回值参数名
)
public void afterReturningAdvice(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" // 指定异常参数名
)
public void afterThrowingAdvice(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.*.*(..))")
public void afterAdvice(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.*.*(..))")
public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
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. 修改横切逻辑需要改动所有方法
*/
public class UserServiceWithoutAOP {
public void addUser(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");
}
// ===== 横切逻辑结束 =====
}
public void deleteUser(String username) {
// 同样的横切逻辑又要写一遍...
System.out.println("[日志] 开始执行deleteUser方法,参数:" + username);
// ... 重复代码 ...
}
// 辅助方法
private boolean checkPermission() { return true; }
private void beginTransaction() { }
private void commitTransaction() { }
private void rollbackTransaction() { }
}✅ 使用AOP的代码(优雅代码)
/**
* 使用AOP的代码:业务逻辑和横切逻辑完全分离
* 优点:
* 1. 代码简洁,只关注业务逻辑
* 2. 横切逻辑集中管理
* 3. 易于维护和扩展
* 4. 修改横切逻辑只需修改切面类
*/
import org.springframework.stereotype.Service;
@Service
public class UserServiceWithAOP {
/**
* 添加用户
* 注意:这个方法只包含业务逻辑
* 日志、权限、事务等横切逻辑由AOP切面自动处理
*/
public void addUser(String username) {
// 纯粹的业务逻辑,简洁明了
System.out.println("执行添加用户的业务逻辑:" + username);
}
/**
* 删除用户
*/
public void deleteUser(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() |
🔍 反射完整示例
/**
* 反射机制完整示例
* 演示反射的各种用法
*/
import java.lang.reflect.*;
/**
* 用户类(作为反射操作的目标)
*/
class User {
// 私有字段
private String username;
private int age;
// 无参构造器
public User() {
System.out.println("无参构造器被调用");
}
// 有参构造器
public User(String username, int age) {
this.username = username;
this.age = age;
System.out.println("有参构造器被调用:" + username + ", " + age);
}
// 公共方法
public void sayHello() {
System.out.println("Hello, I'm " + username);
}
// 私有方法
private void secretMethod() {
System.out.println("这是一个私有方法");
}
// Getter和Setter
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
@Override
public String toString() {
return "User{username='" + username + "', age=" + age + "}";
}
}
/**
* 反射示例类
*/
public class ReflectionDemo {
public static void main(String[] args) throws Exception {
System.out.println("=== 反射机制演示 ===\n");
// 1. 获取Class对象的三种方式
demonstrateGetClass();
// 2. 创建对象
demonstrateCreateInstance();
// 3. 访问和修改字段
demonstrateFieldAccess();
// 4. 调用方法
demonstrateMethodInvocation();
// 5. 获取类信息
demonstrateClassInfo();
}
/**
* 演示获取Class对象的三种方式
*/
private static void demonstrateGetClass() throws Exception {
System.out.println("=== 1. 获取Class对象 ===");
// 方式1:通过类名.class
Class<?> clazz1 = User.class;
System.out.println("方式1(类名.class):" + clazz1.getName());
// 方式2:通过对象.getClass()
User user = new User();
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();
}
/**
* 演示创建对象
*/
private static void demonstrateCreateInstance() throws Exception {
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();
}
/**
* 演示访问和修改字段
*/
private static void demonstrateFieldAccess() throws Exception {
System.out.println("=== 3. 访问和修改字段 ===");
User user = new User("李四", 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();
}
/**
* 演示调用方法
*/
private static void demonstrateMethodInvocation() throws Exception {
System.out.println("=== 4. 调用方法 ===");
User user = new User("赵六", 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();
}
/**
* 演示获取类信息
*/
private static void demonstrateClassInfo() throws Exception {
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:按类型注入 @Autowired private UserService userService; // @Resource:按名称注入 @Resource(name = "userServiceImpl") private UserService 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") // 设置为原型作用域
public class UserService {
// ...
}
💼 AOP相关面试题
Q6: 什么是AOP?AOP的应用场景有哪些?
答案:
AOP(面向切面编程)是一种编程范式,用于将横切关注点(如日志、事务、权限)从业务逻辑中分离出来。
应用场景:
- 日志记录:记录方法调用信息
- 事务管理:自动开启、提交、回滚事务
- 权限验证:检查用户是否有权限执行操作
- 性能监控:统计方法执行时间
- 异常处理:统一处理异常
- 缓存处理:自动缓存方法返回值
Q7: AOP的实现原理是什么?JDK动态代理和CGLIB代理的区别?
答案:
实现原理: Spring AOP使用动态代理技术,在运行时为目标对象创建代理对象,在代理对象中织入横切逻辑。
JDK动态代理 vs CGLIB代理:
| 对比项 | JDK动态代理 | CGLIB代理 |
|---|---|---|
| 原理 | 基于接口,实现InvocationHandler | 基于继承,生成子类 |
| 要求 | 目标类必须实现接口 | 目标类不能是final |
| 性能 | 略慢 | 略快 |
| Spring选择 | 实现了接口时默认使用 | 未实现接口时使用 |
示例:
// JDK动态代理:目标类实现接口
public interface UserService {
void addUser(String username);
}
@Service
public class UserServiceImpl implements UserService {
@Override
public void addUser(String username) {
System.out.println("添加用户:" + username);
}
}
// CGLIB代理:目标类没有实现接口
@Service
public class ProductService {
public void addProduct(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 = new User();
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框架。继续加油!
到此这篇关于Spring IOC 和 AOP 从入门到精通的文章就介绍到这了,更多相关Spring IOC 和 AOP 原理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
IntelliJ IDEA下SpringBoot如何指定某一个配置文件启动项目
这篇文章主要介绍了IntelliJ IDEA下SpringBoot如何指定某一个配置文件启动项目问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教2023-09-09


最新评论