Spring IOC 和 AOP 從入門到精通
?? 博客簡介:本文將深入淺出地講解Spring框架的兩大核心特性——IOC(控制反轉)和AOP(面向切面編程)。適合Spring初學者和準備面試的同學閱讀。
?? 知識目標:理解IOC和AOP的概念、實現(xiàn)機制、應用場景,并掌握依賴注入和反射的原理。
一、Spring IOC(控制反轉)詳解
1.1 什么是IOC?
?? 概念解釋
IOC(Inversion of Control,控制反轉) 是一種設計原則,用于降低代碼之間的耦合度。
?? 通俗理解:想象你是一個老板,以前你需要自己去找員工(new 對象),現(xiàn)在你把這個任務交給HR部門(Spring容器),你只需要告訴HR你需要什么樣的員工(聲明依賴),HR會幫你找到合適的人(注入對象)。這就是"控制反轉"——對象創(chuàng)建的控制權從你的代碼轉移到了Spring容器。
?? 核心思想
傳統(tǒng)開發(fā)模式:
對象A需要對象B → 對象A自己創(chuàng)建對象B(new B())
IOC模式:
對象A需要對象B → 對象A聲明需要B → Spring容器創(chuàng)建B并注入給A
?? 對比示例
傳統(tǒng)方式(緊耦合):
/**
* 傳統(tǒng)方式:UserService 直接創(chuàng)建 UserDao
* 缺點:
* 1. 高耦合:UserService 直接依賴 UserDao 的具體實現(xiàn)
* 2. 難以測試:無法輕易替換 UserDao 的實現(xiàn)
* 3. 不靈活:如果要換一個 UserDao 實現(xiàn),需要修改 UserService 代碼
*/
public class UserService {
// 直接在類內部創(chuàng)建依賴對象
private UserDao userDao = new UserDaoImpl();
public void addUser(String username) {
// 使用 userDao 進行數(shù)據(jù)庫操作
userDao.save(username);
}
}
// UserDao 接口
interface UserDao {
void save(String username);
}
// UserDao 的具體實現(xiàn)
class UserDaoImpl implements UserDao {
@Override
public void save(String username) {
System.out.println("保存用戶:" + username);
}
}IOC方式(松耦合):
/**
* IOC方式:UserService 不再自己創(chuàng)建 UserDao,而是通過依賴注入獲得
* 優(yōu)點:
* 1. 低耦合:UserService 只依賴 UserDao 接口,不關心具體實現(xiàn)
* 2. 易于測試:可以輕松注入模擬對象(Mock)進行單元測試
* 3. 靈活性高:可以通過配置切換不同的 UserDao 實現(xiàn),無需修改代碼
*/
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 的實現(xiàn)類
import org.springframework.stereotype.Repository;
@Repository // 告訴Spring這是一個數(shù)據(jù)訪問層組件,需要被Spring管理
class UserDaoImpl implements UserDao {
@Override
public void save(String username) {
System.out.println("保存用戶到數(shù)據(jù)庫:" + username);
}
}1.2 IOC和AOP是通過什么機制來實現(xiàn)的?
??? IOC的實現(xiàn)機制
Spring IOC容器主要通過以下技術實現(xiàn):
- 反射(Reflection):動態(tài)創(chuàng)建對象
- 工廠模式:管理Bean的生命周期
- 注解或XML配置:描述Bean之間的依賴關系
?? IOC容器工作流程
┌─────────────────────────────────────────────────────┐ │ Spring IOC容器 │ │ │ │ 1. 讀取配置(XML/注解) │ │ ↓ │ │ 2. 解析Bean定義信息 │ │ ↓ │ │ 3. 使用反射創(chuàng)建Bean實例 │ │ ↓ │ │ 4. 進行依賴注入(屬性賦值) │ │ ↓ │ │ 5. 初始化Bean(調用初始化方法) │ │ ↓ │ │ 6. 將Bean放入容器(可供使用) │ │ ↓ │ │ 7. 容器關閉時銷毀Bean │ └─────────────────────────────────────────────────────┘
?? AOP的實現(xiàn)機制
Spring AOP主要通過以下技術實現(xiàn):
- 動態(tài)代理(Dynamic Proxy):
- JDK動態(tài)代理:針對實現(xiàn)了接口的類
- CGLIB代理:針對沒有實現(xiàn)接口的類
- 字節(jié)碼增強:在運行時修改或生成字節(jié)碼
?? 動態(tài)代理原理圖
原始對象(Target)
↓
Spring AOP 檢測到需要增強
↓
創(chuàng)建代理對象(Proxy)
↓
┌─────────────────────────────────┐
│ 代理對象 │
│ ┌──────────────────────────┐ │
│ │ 前置通知(Before) │ │
│ └──────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────┐ │
│ │ 調用原始方法 │ │
│ └──────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────┐ │
│ │ 后置通知(After) │ │
│ └──────────────────────────┘ │
└─────────────────────────────────┘1.3 怎么理解Spring IOC?
?? 核心理解
Spring IOC的本質是一個對象工廠,它負責:
- 創(chuàng)建對象:根據(jù)配置信息創(chuàng)建Bean實例
- 管理對象:管理對象的生命周期(從創(chuàng)建到銷毀)
- 裝配對象:處理對象之間的依賴關系
?? IOC容器的兩種類型
| 容器類型 | 說明 | 應用場景 |
|---|---|---|
| BeanFactory | 基礎容器,延遲加載Bean | 資源受限的環(huán)境 |
| ApplicationContext | 高級容器,啟動時加載所有Bean | 企業(yè)級應用(推薦) |
?? 生活類比:BeanFactory像外賣平臺,你點餐(調用getBean)時才開始做菜(創(chuàng)建Bean);ApplicationContext像自助餐廳,所有菜品(Bean)在開門時就已經準備好了。
1.4 依賴注入(DI)詳解
?? 什么是依賴注入?
依賴注入(Dependency Injection,DI) 是IOC的一種實現(xiàn)方式,它是指:由Spring容器在運行期間,動態(tài)地將依賴關系注入到對象中。
?? 通俗理解:你開了一家餐廳(類),需要廚師(依賴對象)。傳統(tǒng)方式是你自己去招聘(new對象),而依賴注入就是HR(Spring容器)直接把合格的廚師送到你的餐廳。
?? 依賴注入的三種方式
1?? 構造器注入(Constructor Injection)
特點:通過構造方法注入依賴,適合必需的、不可變的依賴。
/**
* 構造器注入示例
* 優(yōu)點:
* 1. 依賴關系明確,必須在對象創(chuàng)建時提供
* 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會自動傳入這兩個參數(shù)
this.userService = userService;
this.productService = productService;
System.out.println("OrderService通過構造器注入創(chuàng)建");
}
/**
* 創(chuàng)建訂單的業(yè)務方法
*/
public void createOrder(String username, String productName) {
// 使用注入的依賴
userService.addUser(username);
System.out.println("訂單創(chuàng)建成功:用戶=" + username + ", 商品=" + productName);
}
}2?? Setter方法注入(Setter Injection)
特點:通過setter方法注入依賴,適合可選的、可變的依賴。
/**
* Setter注入示例
* 優(yōu)點:
* 1. 靈活性高,可以在對象創(chuàng)建后修改依賴
* 2. 可以注入可選依賴
*
* 缺點:
* 1. 可能造成對象處于不完整狀態(tài)
* 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 郵件發(fā)送器
*/
@Autowired
public void setMailSender(MailSender mailSender) {
this.mailSender = mailSender;
System.out.println("通過Setter方法注入MailSender");
}
/**
* 發(fā)送郵件
*/
public void sendEmail(String to, String content) {
if (mailSender != null) {
mailSender.send(to, content);
} else {
System.out.println("MailSender未注入,無法發(fā)送郵件");
}
}
}3?? 字段注入(Field Injection)
特點:直接在字段上使用@Autowired注解,最簡潔但不推薦。
/**
* 字段注入示例
* 優(yōu)點:
* 1. 代碼最簡潔
* 2. 使用方便
*
* 缺點:
* 1. 難以進行單元測試(無法輕松注入Mock對象)
* 2. 違反了依賴注入的原則(依賴關系不明確)
* 3. 無法聲明為final
* 4. IDE會給出警告
*
* 適用場景:快速開發(fā)、原型項目(生產環(huán)境不推薦)
*/
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);
}
}?? 三種注入方式對比
| 注入方式 | 語法復雜度 | 測試友好度 | 依賴明確性 | 推薦度 | 適用場景 |
|---|---|---|---|---|---|
| 構造器注入 | 中等 | ????? | ????? | ????? | 必需依賴、生產環(huán)境 |
| Setter注入 | 簡單 | ???? | ??? | ??? | 可選依賴 |
| 字段注入 | 最簡單 | ?? | ?? | ?? | 快速開發(fā)、原型 |
?? 最佳實踐:優(yōu)先使用構造器注入,它是Spring官方推薦的方式。
二、Spring AOP(面向切面編程)詳解
2.1 什么是AOP?
?? 概念解釋
AOP(Aspect Oriented Programming,面向切面編程) 是一種編程范式,它允許程序員模塊化橫切關注點(Cross-cutting Concerns)。
?? 通俗理解:想象你在開發(fā)一個電商系統(tǒng),每個功能(登錄、下單、支付)都需要記錄日志、檢查權限、統(tǒng)計性能。如果在每個方法里都寫這些代碼,會非常重復。AOP就像一個"管家",它可以在方法執(zhí)行前后自動幫你做這些事,而你只需要專注于業(yè)務邏輯。
?? 核心概念
| 概念 | 英文 | 說明 | 舉例 |
|---|---|---|---|
| 切面 | Aspect | 橫切關注點的模塊化 | 日志切面、事務切面 |
| 連接點 | Join Point | 程序執(zhí)行的某個點 | 方法調用、異常拋出 |
| 切點 | Pointcut | 匹配連接點的表達式 | 所有Service層的方法 |
| 通知 | Advice | 在切點執(zhí)行的代碼 | 前置通知、后置通知 |
| 目標對象 | Target | 被增強的對象 | UserService實例 |
| 代理對象 | Proxy | AOP創(chuàng)建的對象 | UserService的代理 |
| 織入 | Weaving | 將切面應用到目標的過程 | Spring在運行時織入 |
?? AOP概念圖解
切面(Aspect)
┌─────────────────────┐
│ @Aspect │
│ LoggingAspect │
│ │
│ 切點(Pointcut) │
│ ↓ │
│ execution(* com.example..*(..)) │
│ │
│ 通知(Advice) │
│ ↓ │
│ @Before │
│ @After │
│ @Around │
└─────────────────────┘
↓
織入(Weaving)
↓
┌─────────────────────┐
│ 目標對象(Target) │
│ UserService │
└─────────────────────┘
↓
┌─────────────────────┐
│ 代理對象(Proxy) │
│ UserServiceProxy │
└─────────────────────┘?? AOP的五種通知類型
/**
* AOP通知類型完整示例
* 演示五種通知類型的執(zhí)行時機和用法
*/
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)
* 在目標方法執(zhí)行之前執(zhí)行
*
* 使用場景:參數(shù)驗證、權限檢查、日志記錄
*
* @param joinPoint 連接點,包含方法信息
*/
@Before("execution(* com.example.service.*.*(..))")
public void beforeAdvice(JoinPoint joinPoint) {
// 獲取方法名
String methodName = joinPoint.getSignature().getName();
// 獲取參數(shù)
Object[] args = joinPoint.getArgs();
System.out.println("=== 前置通知 ===");
System.out.println("即將執(zhí)行方法:" + methodName);
System.out.println("方法參數(shù):" + java.util.Arrays.toString(args));
}
/**
* 2. 后置通知(After Returning Advice)
* 在目標方法正常返回后執(zhí)行(不包括異常情況)
*
* 使用場景:記錄返回值、數(shù)據(jù)處理、緩存更新
*
* @param joinPoint 連接點
* @param result 方法返回值
*/
@AfterReturning(
pointcut = "execution(* com.example.service.*.*(..))",
returning = "result" // 指定返回值參數(shù)名
)
public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
System.out.println("=== 后置通知 ===");
System.out.println("方法執(zhí)行成功:" + methodName);
System.out.println("返回值:" + result);
}
/**
* 3. 異常通知(After Throwing Advice)
* 在目標方法拋出異常后執(zhí)行
*
* 使用場景:異常日志記錄、異常轉換、告警通知
*
* @param joinPoint 連接點
* @param exception 拋出的異常
*/
@AfterThrowing(
pointcut = "execution(* com.example.service.*.*(..))",
throwing = "exception" // 指定異常參數(shù)名
)
public void afterThrowingAdvice(JoinPoint joinPoint, Exception exception) {
String methodName = joinPoint.getSignature().getName();
System.out.println("=== 異常通知 ===");
System.out.println("方法執(zhí)行異常:" + methodName);
System.out.println("異常信息:" + exception.getMessage());
}
/**
* 4. 最終通知(After Advice)
* 在目標方法執(zhí)行之后執(zhí)行(無論是否拋出異常)
* 類似于finally塊
*
* 使用場景:資源釋放、清理工作
*
* @param joinPoint 連接點
*/
@After("execution(* com.example.service.*.*(..))")
public void afterAdvice(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("=== 最終通知 ===");
System.out.println("方法執(zhí)行完畢:" + methodName);
}
/**
* 5. 環(huán)繞通知(Around Advice)
* 包圍目標方法,可以控制方法的執(zhí)行
* 最強大的通知類型
*
* 使用場景:性能統(tǒng)計、事務管理、緩存處理
*
* @param proceedingJoinPoint 可執(zhí)行的連接點
* @return 方法返回值
* @throws Throwable 方法可能拋出的異常
*/
@Around("execution(* com.example.service.*.*(..))")
public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
String methodName = proceedingJoinPoint.getSignature().getName();
System.out.println("=== 環(huán)繞通知開始 ===");
System.out.println("準備執(zhí)行方法:" + methodName);
// 記錄開始時間
long startTime = System.currentTimeMillis();
Object result = null;
try {
// 執(zhí)行目標方法(這是關鍵?。?
result = proceedingJoinPoint.proceed();
// 目標方法執(zhí)行成功
System.out.println("方法執(zhí)行成功,返回值:" + result);
} catch (Throwable throwable) {
// 目標方法執(zhí)行失敗
System.out.println("方法執(zhí)行失?。? + throwable.getMessage());
throw throwable; // 重新拋出異常
} finally {
// 計算執(zhí)行時間
long endTime = System.currentTimeMillis();
long executionTime = endTime - startTime;
System.out.println("方法執(zhí)行耗時:" + executionTime + "ms");
System.out.println("=== 環(huán)繞通知結束 ===");
}
// 返回結果(可以修改返回值)
return result;
}
}?? 通知執(zhí)行順序
正常執(zhí)行流程:
@Around 開始
↓
@Before
↓
目標方法執(zhí)行
↓
@AfterReturning
↓
@After
↓
@Around 結束
異常執(zhí)行流程:
@Around 開始
↓
@Before
↓
目標方法執(zhí)行(拋出異常)
↓
@AfterThrowing
↓
@After
↓
@Around 異常處理2.2 Spring AOP主要想解決什么問題?
?? 解決的核心問題
Spring AOP主要解決代碼重復和橫切關注點分離的問題。
?? 橫切關注點示例
業(yè)務邏輯層 ┌─────────────────────────┐ │ │ │ UserService │ ← 日志、事務、權限 │ OrderService │ ← 日志、事務、權限 │ ProductService │ ← 日志、事務、權限 │ PaymentService │ ← 日志、事務、權限 │ │ └─────────────────────────┘ 橫切關注點: - 日志記錄 - 事務管理 - 權限檢查 - 性能監(jiān)控
? 沒有AOP的代碼(問題代碼)
/**
* 沒有AOP的代碼:每個方法都要寫重復的橫切邏輯
* 問題:
* 1. 代碼重復
* 2. 業(yè)務邏輯和橫切邏輯混在一起
* 3. 難以維護
* 4. 修改橫切邏輯需要改動所有方法
*/
public class UserServiceWithoutAOP {
public void addUser(String username) {
// ===== 橫切邏輯開始 =====
// 1. 日志記錄
System.out.println("[日志] 開始執(zhí)行addUser方法,參數(shù):" + username);
long startTime = System.currentTimeMillis();
try {
// 2. 權限檢查
if (!checkPermission()) {
System.out.println("[權限] 無權限執(zhí)行此操作");
return;
}
// 3. 事務開啟
beginTransaction();
// ===== 業(yè)務邏輯開始 =====
System.out.println("執(zhí)行添加用戶的業(yè)務邏輯:" + username);
// 實際的業(yè)務代碼...
// ===== 業(yè)務邏輯結束 =====
// 4. 事務提交
commitTransaction();
} catch (Exception e) {
// 5. 事務回滾
rollbackTransaction();
System.out.println("[異常] 方法執(zhí)行失?。? + e.getMessage());
} finally {
// 6. 性能統(tǒng)計
long endTime = System.currentTimeMillis();
System.out.println("[性能] 方法執(zhí)行耗時:" + (endTime - startTime) + "ms");
}
// ===== 橫切邏輯結束 =====
}
public void deleteUser(String username) {
// 同樣的橫切邏輯又要寫一遍...
System.out.println("[日志] 開始執(zhí)行deleteUser方法,參數(shù):" + username);
// ... 重復代碼 ...
}
// 輔助方法
private boolean checkPermission() { return true; }
private void beginTransaction() { }
private void commitTransaction() { }
private void rollbackTransaction() { }
}? 使用AOP的代碼(優(yōu)雅代碼)
/**
* 使用AOP的代碼:業(yè)務邏輯和橫切邏輯完全分離
* 優(yōu)點:
* 1. 代碼簡潔,只關注業(yè)務邏輯
* 2. 橫切邏輯集中管理
* 3. 易于維護和擴展
* 4. 修改橫切邏輯只需修改切面類
*/
import org.springframework.stereotype.Service;
@Service
public class UserServiceWithAOP {
/**
* 添加用戶
* 注意:這個方法只包含業(yè)務邏輯
* 日志、權限、事務等橫切邏輯由AOP切面自動處理
*/
public void addUser(String username) {
// 純粹的業(yè)務邏輯,簡潔明了
System.out.println("執(zhí)行添加用戶的業(yè)務邏輯:" + username);
}
/**
* 刪除用戶
*/
public void deleteUser(String username) {
// 純粹的業(yè)務邏輯
System.out.println("執(zhí)行刪除用戶的業(yè)務邏輯:" + username);
}
}三、反射機制詳解
3.1 什么是反射?
?? 概念解釋
反射(Reflection) 是Java提供的一種能力,允許程序在運行時檢查和操作類、方法、字段等信息,即使在編譯時不知道它們的名稱。
?? 通俗理解:想象你是一個偵探,反射就像是一個X光機,可以讓你在程序運行時"透視"一個對象,看到它的內部結構(字段)、能力(方法)、身份(類型),甚至可以修改它們。
?? 反射的核心功能
- 獲取類信息:獲取類的名稱、父類、接口、修飾符等
- 創(chuàng)建對象:動態(tài)創(chuàng)建類的實例
- 訪問字段:獲取和修改對象的字段值(包括私有字段)
- 調用方法:動態(tài)調用對象的方法(包括私有方法)
- 操作數(shù)組:動態(tài)創(chuàng)建和操作數(shù)組
?? 反射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. 創(chuàng)建對象
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();
}
/**
* 演示創(chuàng)建對象
*/
private static void demonstrateCreateInstance() throws Exception {
System.out.println("=== 2. 創(chuàng)建對象 ===");
Class<?> clazz = User.class;
// 方式1:使用無參構造器創(chuàng)建對象
System.out.println("方式1:使用無參構造器");
User user1 = (User) clazz.getDeclaredConstructor().newInstance();
// 方式2:使用有參構造器創(chuàng)建對象
System.out.println("\n方式2:使用有參構造器");
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
User user2 = (User) constructor.newInstance("張三", 25);
System.out.println("創(chuàng)建的對象:" + 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 反射的使用場景
?? 主要使用場景
| 場景 | 說明 | 示例 |
|---|---|---|
| 框架開發(fā) | Spring IOC容器使用反射創(chuàng)建Bean | Spring, Hibernate |
| 動態(tài)代理 | AOP使用反射實現(xiàn)動態(tài)代理 | Spring AOP, MyBatis |
| 注解處理 | 讀取和處理注解 | JUnit, Spring |
| 序列化/反序列化 | JSON與對象互轉 | Jackson, Gson, Fastjson |
| ORM框架 | 數(shù)據(jù)庫記錄映射為對象 | Hibernate, MyBatis |
| 插件系統(tǒng) | 動態(tài)加載和執(zhí)行插件 | Eclipse插件, IDEA插件 |
| 工具類 | 通用的工具方法 | BeanUtils, PropertyUtils |
?? 反射的優(yōu)缺點
優(yōu)點:
- ? 增加程序的靈活性,可以在運行時動態(tài)操作
- ? 降低耦合度,無需在編譯時確定具體類型
- ? 支持動態(tài)創(chuàng)建對象和調用方法
缺點:
- ? 性能開銷大,比直接調用慢10-100倍
- ? 破壞封裝性,可以訪問私有成員
- ? 代碼復雜度高,難以理解和維護
五、常見面試問題匯總
?? IOC相關面試題
Q1: 什么是IOC?IOC有什么好處?
答案:
IOC(控制反轉)是一種設計原則,將對象創(chuàng)建和依賴關系的管理從應用程序代碼轉移到容器中。
好處:
- 降低耦合度:對象之間不直接依賴,而是通過接口依賴
- 提高可測試性:可以輕松注入Mock對象進行單元測試
- 提高可維護性:修改依賴關系只需修改配置,無需修改代碼
- 統(tǒng)一管理:所有對象的生命周期由容器統(tǒng)一管理
Q2: IOC和DI的區(qū)別是什么?
答案:
- IOC(控制反轉):是一種設計思想,指將對象創(chuàng)建的控制權交給容器
- DI(依賴注入):是IOC的一種實現(xiàn)方式,指容器在運行時動態(tài)地將依賴注入到對象中
關系: IOC是目標,DI是實現(xiàn)IOC的手段。
Q3: Spring IOC容器的初始化過程是怎樣的?
答案:
- 加載配置:讀取XML配置文件或掃描注解
- 解析配置:將配置信息解析為BeanDefinition對象
- 注冊BeanDefinition:將BeanDefinition注冊到容器中
- 實例化Bean:使用反射創(chuàng)建Bean實例
- 屬性賦值:進行依賴注入,為Bean的屬性賦值
- 初始化Bean:調用初始化方法(如@PostConstruct、InitializingBean)
- 使用Bean:Bean可以被應用程序使用
- 銷毀Bean:容器關閉時調用銷毀方法(如@PreDestroy、DisposableBean)
Q4: @Autowired和@Resource的區(qū)別?
答案:
| 對比項 | @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 | 單例(默認) | 無狀態(tài)的Bean(Service、Dao) |
| prototype | 每次獲取創(chuàng)建新實例 | 有狀態(tài)的Bean |
| request | 每次HTTP請求創(chuàng)建 | Web應用(請求級數(shù)據(jù)) |
| session | 每個Session創(chuàng)建 | Web應用(用戶會話數(shù)據(jù)) |
| application | 整個ServletContext創(chuàng)建 | Web應用(全局數(shù)據(jù)) |
| websocket | 每個WebSocket創(chuàng)建 | WebSocket應用 |
示例:
@Service
@Scope("prototype") // 設置為原型作用域
public class UserService {
// ...
}
?? AOP相關面試題
Q6: 什么是AOP?AOP的應用場景有哪些?
答案:
AOP(面向切面編程)是一種編程范式,用于將橫切關注點(如日志、事務、權限)從業(yè)務邏輯中分離出來。
應用場景:
- 日志記錄:記錄方法調用信息
- 事務管理:自動開啟、提交、回滾事務
- 權限驗證:檢查用戶是否有權限執(zhí)行操作
- 性能監(jiān)控:統(tǒng)計方法執(zhí)行時間
- 異常處理:統(tǒng)一處理異常
- 緩存處理:自動緩存方法返回值
Q7: AOP的實現(xiàn)原理是什么?JDK動態(tài)代理和CGLIB代理的區(qū)別?
答案:
實現(xiàn)原理: Spring AOP使用動態(tài)代理技術,在運行時為目標對象創(chuàng)建代理對象,在代理對象中織入橫切邏輯。
JDK動態(tài)代理 vs CGLIB代理:
| 對比項 | JDK動態(tài)代理 | CGLIB代理 |
|---|---|---|
| 原理 | 基于接口,實現(xiàn)InvocationHandler | 基于繼承,生成子類 |
| 要求 | 目標類必須實現(xiàn)接口 | 目標類不能是final |
| 性能 | 略慢 | 略快 |
| Spring選擇 | 實現(xiàn)了接口時默認使用 | 未實現(xiàn)接口時使用 |
示例:
// JDK動態(tài)代理:目標類實現(xiàn)接口
public interface UserService {
void addUser(String username);
}
@Service
public class UserServiceImpl implements UserService {
@Override
public void addUser(String username) {
System.out.println("添加用戶:" + username);
}
}
// CGLIB代理:目標類沒有實現(xiàn)接口
@Service
public class ProductService {
public void addProduct(String productName) {
System.out.println("添加商品:" + productName);
}
}Q8: AOP的五種通知類型分別在什么時候執(zhí)行?
答案:
| 通知類型 | 注解 | 執(zhí)行時機 | 使用場景 |
|---|---|---|---|
| 前置通知 | @Before | 方法執(zhí)行前 | 參數(shù)驗證、權限檢查 |
| 后置通知 | @AfterReturning | 方法正常返回后 | 記錄返回值、緩存更新 |
| 異常通知 | @AfterThrowing | 方法拋出異常后 | 異常日志、告警通知 |
| 最終通知 | @After | 方法執(zhí)行后(無論是否異常) | 資源釋放、清理工作 |
| 環(huán)繞通知 | @Around | 包圍整個方法 | 性能統(tǒng)計、事務管理 |
執(zhí)行順序(正常情況):
@Around 開始 → @Before → 目標方法 → @AfterReturning → @After → @Around 結束
Q9: 切點表達式怎么寫?
答案:
語法格式:
execution(修飾符? 返回類型 包名.類名.方法名(參數(shù)) 異常?)
常用通配符:
*:匹配任意字符..:匹配任意層級的包或任意個參數(shù)+:匹配指定類及其子類
示例:
// 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參數(shù)的方法 execution(* com.example.service.*Service.*(String)) // 7. 匹配第一個參數(shù)為String的方法 execution(* com.example.service.*Service.*(String,..))
?? 反射相關面試題
Q10: 什么是反射?反射有什么應用?
答案:
反射是Java提供的一種能力,允許程序在運行時檢查和操作類、方法、字段等信息。
應用:
- Spring框架:IOC容器使用反射創(chuàng)建Bean
- ORM框架:Hibernate、MyBatis使用反射映射數(shù)據(jù)
- 動態(tài)代理:AOP使用反射實現(xiàn)
- 序列化:JSON庫使用反射轉換對象
- 單元測試:Junit使用反射執(zhí)行測試方法
- 注解處理:框架使用反射讀取注解
Q11: 反射的性能問題如何優(yōu)化?
答案:
性能問題原因:
- 反射需要查找類信息,比直接調用慢
- 需要進行權限檢查
- 涉及到對象包裝/拆包
優(yōu)化方案:
- 緩存反射結果:緩存Class、Method、Field對象
- 關閉安全檢查:
method.setAccessible(true) - 使用MethodHandle:Java 7+提供的更高效的方式
- 減少反射使用:能直接調用就不用反射
- 使用反射工具:Apache Commons BeanUtils
示例:
// 優(yōu)化前:每次都獲取Method對象
for (int i = 0; i < 10000; i++) {
Method method = clazz.getMethod("sayHello");
method.invoke(user);
}
// 優(yōu)化后:緩存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(控制反轉)
- 將對象創(chuàng)建的控制權交給Spring容器
- 通過依賴注入實現(xiàn)
- 優(yōu)先使用構造器注入
- AOP(面向切面編程)
- 用于模塊化橫切關注點
- 通過動態(tài)代理實現(xiàn)
- 五種通知類型,環(huán)繞通知最強大
- 反射(Reflection)
- 運行時操作類和對象
- Spring框架的基礎技術
- 注意性能問題,合理使用緩存
學習建議
- ?? 理論學習:先理解概念,再看源碼實現(xiàn)
- ?? 實踐練習:動手寫代碼,加深理解
- ?? 源碼閱讀:閱讀Spring源碼,學習設計思想
- ?? 總結歸納:整理筆記,形成知識體系
?? 恭喜你! 你已經完成了Spring IOC和AOP的學習。希望這篇博客能幫助你更好地理解和使用Spring框架。繼續(xù)加油!
到此這篇關于Spring IOC 和 AOP 從入門到精通的文章就介紹到這了,更多相關Spring IOC 和 AOP 原理內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SpringBoot定時監(jiān)聽RocketMQ的NameServer問題及解決方案
這篇文章主要介紹了SpringBoot定時監(jiān)聽RocketMQ的NameServer問題及解決方案,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧2023-12-12
JAVA實現(xiàn)Token自動續(xù)期機制的示例代碼
本文主要介紹了JAVA實現(xiàn)Token自動續(xù)期機制的示例代碼,通過動態(tài)調整會話生命周期平衡安全性與用戶體驗,解決固定有效期Token帶來的風險與不便,感興趣的可以了解一下2025-09-09
SpringBoot常見get/post請求參數(shù)處理、參數(shù)注解校驗及參數(shù)自定義注解校驗詳解
這篇文章主要給大家介紹了關于SpringBoot常見get/post請求參數(shù)處理、參數(shù)注解校驗及參數(shù)自定義注解校驗的相關資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2022-03-03

