Spring依賴注入與自動(dòng)裝配原理解析
寫在前面
如果你想深入學(xué)習(xí)SpringBoot的自動(dòng)配置原理,那么本文是你必須掌握的基礎(chǔ)知識。很多人在使用SpringBoot時(shí),只知道用@Autowired注入依賴,卻不知道背后的原理。
本文將從最基礎(chǔ)的概念講起,帶你徹底理解Spring的依賴注入機(jī)制。無論你是剛接觸Spring的新手,還是想復(fù)習(xí)鞏固的老手,都能從本文獲益。
適合人群: Spring初學(xué)者、準(zhǔn)備深入學(xué)習(xí)SpringBoot的開發(fā)者、需要復(fù)習(xí)基礎(chǔ)的工程師
一、核心概念:什么是Bean、依賴和注入
在學(xué)習(xí)依賴注入之前,我們必須先理解三個(gè)基本概念。
1.1 什么是Bean?
Bean就是由Spring容器管理的對象。
在傳統(tǒng)Java開發(fā)中,我們這樣創(chuàng)建對象:
UserService userService = new UserService();
而在Spring中,我們不需要自己new對象,而是讓Spring容器幫我們創(chuàng)建和管理:
@Service // 告訴Spring:這是一個(gè)Bean,請幫我管理。@Service是@Component的衍生注解
public class UserService {
// ...
}
形象的比喻:
- 傳統(tǒng)方式:你需要什么對象,自己new一個(gè)(自己做飯)
- Spring方式:你告訴Spring需要什么,Spring給你創(chuàng)建好(外賣送餐)
Bean的特點(diǎn):
- 由Spring容器創(chuàng)建
- 由Spring容器管理生命周期
- 默認(rèn)是單例的(整個(gè)應(yīng)用只有一個(gè)實(shí)例)
- 可以被其他Bean引用
1.2 什么是依賴?
依賴就是一個(gè)對象需要使用另一個(gè)對象來完成工作。
舉個(gè)例子:
public class UserService {
private UserDao userDao; // UserService依賴UserDao
public User getUser(Long id) {
return userDao.findById(id); // 需要用到UserDao
}
}在這個(gè)例子中:
UserService需要UserDao才能完成查詢用戶的功能- 我們說:UserService 依賴 UserDao
生活中的例子:
- 你要開車上班,你依賴汽車
- 你要做飯,你依賴廚具
- 你要寫代碼,你依賴電腦
1.3 什么是注入依賴?
注入依賴就是把一個(gè)對象需要的其他對象"送"給它。
傳統(tǒng)方式(自己創(chuàng)建依賴):
public class UserService {
private UserDao userDao;
public UserService() {
this.userDao = new UserDaoImpl(); // 自己創(chuàng)建依賴
}
}依賴注入方式(Spring幫你注入):
@Service
public class UserService {
@Autowired // 告訴Spring:請把UserDao注入給我
private UserDao userDao;
}形象的比喻:
- 傳統(tǒng)方式:你要喝咖啡,自己買豆子、磨豆、煮咖啡(自己創(chuàng)建依賴)
- 依賴注入:你要喝咖啡,告訴咖啡店,咖啡店做好送給你(Spring注入依賴)
依賴注入的好處:
- 解耦:UserService不需要知道UserDao的具體實(shí)現(xiàn)
- 易測試:可以輕松替換為Mock對象進(jìn)行測試
- 易維護(hù):要換實(shí)現(xiàn),不需要修改UserService代碼
- 統(tǒng)一管理:所有對象由Spring容器統(tǒng)一創(chuàng)建和管理
二、控制反轉(zhuǎn)(IoC):理解Spring的核心思想
2.1 什么是控制反轉(zhuǎn)?
控制反轉(zhuǎn)(Inversion of Control,IoC)是一種設(shè)計(jì)思想:把對象創(chuàng)建和管理的控制權(quán)交給框架。
簡單來說:就相當(dāng)于框架幫你創(chuàng)建對象,只創(chuàng)建一次,那個(gè)類調(diào)用就把對象注入給誰
好處:
- 徹底解放雙手:不用再手動(dòng)
new對象、手動(dòng)傳依賴 - 統(tǒng)一管理對象:單例復(fù)用 + 生命周期全托管
- 單例復(fù)用:框架創(chuàng)建的 Bean 默認(rèn)是單例(只創(chuàng)建 1 次),所有需要這個(gè)對象的類,注入的都是同一個(gè)實(shí)例,避免重復(fù)創(chuàng)建對象浪費(fèi)內(nèi)存(比如一個(gè)
UserDao對象,所有Service都用同一個(gè),不用每次都new); - 生命周期托管:對象的 “創(chuàng)建→初始化→使用→銷毀” 全由框架控制,你可以通過注解(如
@PostConstruct初始化、@PreDestroy銷毀)自定義流程,不用手動(dòng)管理對象的生滅。
- 降低代碼耦合:類之間解耦,項(xiàng)目易維護(hù)、易擴(kuò)展
傳統(tǒng)方式: 你控制一切
public class UserService {
private UserDao userDao;
public UserService() {
// 你決定創(chuàng)建什么對象
this.userDao = new UserDaoImpl();
}
}IoC方式: Spring控制一切
@Service
public class UserService {
@Autowired
private UserDao userDao; // Spring決定注入什么對象
}"反轉(zhuǎn)"體現(xiàn)在哪里?
| 維度 | 傳統(tǒng)方式 | IoC方式 |
|---|---|---|
| 誰創(chuàng)建對象 | 你自己new | Spring容器創(chuàng)建 |
| 誰管理對象 | 你自己管理 | Spring容器管理 |
| 誰決定依賴 | 你在代碼中寫死 | Spring根據(jù)配置決定 |
核心思想: 不要打電話給我們,我們會(huì)打電話給你(Don’t call us, we’ll call you)
2.2 IoC容器的作用
Spring的IoC容器(ApplicationContext)就像一個(gè)對象工廠,負(fù)責(zé):
- 創(chuàng)建對象:根據(jù)配置創(chuàng)建Bean
- 管理生命周期:從創(chuàng)建到銷毀
- 注入依賴:自動(dòng)裝配Bean之間的依賴關(guān)系
- 提供服務(wù):提供Bean的查找、獲取等功能
// Spring容器啟動(dòng)時(shí) ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); // 從容器獲取Bean UserService userService = context.getBean(UserService.class); // 這個(gè)userService已經(jīng)注入好了所有依賴,可以直接使用 User user = userService.getUser(1L);
三、依賴注入的三種方式
Spring支持三種依賴注入方式,每種都有各自的適用場景。
方式1:字段注入(最常見但不推薦)
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Autowired
private EmailService emailService;
}優(yōu)點(diǎn): 代碼簡潔,寫起來方便
缺點(diǎn):
- 無法注入final字段(不能保證不可變性)
- 不利于單元測試(必須啟動(dòng)Spring容器或使用反射)
- 違反了面向?qū)ο蟮姆庋b原則
- 依賴關(guān)系不明確
方式2:構(gòu)造器注入(強(qiáng)烈推薦)
@Service
//@RequiredArgsConstructor
//Lombok注解,寫了這個(gè)就不用寫構(gòu)造器了,但是要加入Lombok依賴。(依賴在博主之前的關(guān)于Lombok的文章中有提到過)
public class UserService {
private final UserDao userDao;
private final EmailService emailService;
// Spring 4.3+ 單構(gòu)造器可省略@Autowired
public UserService(UserDao userDao, EmailService emailService) {
this.userDao = userDao;
this.emailService = emailService;
}
}優(yōu)點(diǎn):
- 可以使用final,保證不可變性和線程安全
- 便于單元測試(直接new對象傳參,無需Spring容器)
- 依賴關(guān)系一目了然
- 如果依賴過多,構(gòu)造器參數(shù)會(huì)很長,提醒你類職責(zé)可能過重
這是Spring官方推薦的方式!
方式3:Setter方法注入
@Service
public class UserService {
private UserDao userDao;
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}優(yōu)點(diǎn): 可以在對象創(chuàng)建后重新注入依賴(適合可選依賴)
缺點(diǎn): 依賴可能為null,不夠安全
三種方式對比:
| 特性 | 字段注入 | 構(gòu)造器注入 | Setter注入 |
|---|---|---|---|
| 代碼簡潔度 | 最簡潔 | 中等 | 較繁瑣 |
| 是否支持final | 否 | 是 | 否 |
| 測試友好度 | 差 | 好 | 中等 |
| 依賴明確度 | 不明確 | 非常明確 | 較明確 |
| 官方推薦度 | 不推薦 | 強(qiáng)烈推薦 | 適合可選依賴 |
四、@Autowired與自動(dòng)裝配詳解
4.1 什么是自動(dòng)裝配?
自動(dòng)裝配(Auto-Wiring)是Spring自動(dòng)為Bean注入依賴的機(jī)制。
簡單來說:你只需要告訴Spring"我需要這個(gè)依賴"(通過@Autowired注解),Spring會(huì)自動(dòng)幫你找到合適的Bean并注入進(jìn)來。
傳統(tǒng)方式(手動(dòng)裝配):
<bean id="userDao" class="com.example.dao.UserDaoImpl"/>
<bean id="userService" class="com.example.service.UserService">
<property name="userDao" ref="userDao"/> <!-- 手動(dòng)指定依賴 -->
</bean>自動(dòng)裝配方式:
@Service
public class UserService {
@Autowired // 自動(dòng)裝配,Spring自動(dòng)找到UserDao并注入
private UserDao userDao;
}
4.2 自動(dòng)裝配的演進(jìn)歷史
階段1:XML時(shí)代(Spring 1.x - 2.x)
<!-- 按類型自動(dòng)裝配 -->
<bean id="userService" class="com.example.service.UserService"
autowire="byType"/>
<!-- 按名稱自動(dòng)裝配 -->
<bean id="userService" class="com.example.service.UserService"
autowire="byName"/>階段2:注解時(shí)代(Spring 2.5+)
@Service
public class UserService {
@Autowired // 注解方式,更簡潔
private UserDao userDao;
}
階段3:純Java配置時(shí)代(Spring 3.0+)
Spring 3.0 引入了 @Configuration 和 @Bean:
@Configuration
public class AppConfig {
@Bean
public UserDao userDao() {
return new UserDaoImpl();
}
@Bean
public UserService userService() {
return new UserService(); // 自動(dòng)裝配
}
}階段4:SpringBoot時(shí)代(Spring 4.0+)
@SpringBootApplication // 自動(dòng)掃描,自動(dòng)裝配
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
4.3 自動(dòng)裝配的工作原理
核心處理器: AutowiredAnnotationBeanPostProcessor
完整流程:
1. Spring容器啟動(dòng),掃描所有Bean定義 ↓ 2. 創(chuàng)建Bean實(shí)例(調(diào)用構(gòu)造函數(shù)) ↓ 3. AutowiredAnnotationBeanPostProcessor介入 ↓ 4. 掃描Bean中的@Autowired注解 ↓ 5. 解析依賴類型 ↓ 6. 在容器中查找匹配的Bean ├─ 按類型查找(byType)- 默認(rèn)策略 ├─ 如果有多個(gè),按@Primary篩選 ├─ 如果還有多個(gè),按@Qualifier篩選 └─ 如果還有多個(gè),按字段名稱匹配 ↓ 7. 通過反射注入依賴 ↓ 8. Bean初始化完成
4.4 自動(dòng)裝配的匹配策略
策略1:按類型匹配(byType)- 默認(rèn)
@Service
public class UserService {
@Autowired
private UserDao userDao; // 查找UserDao類型的Bean
}
- 找到1個(gè) → 直接注入
- 找到0個(gè) → 拋出異常(除非required=false)
- 找到多個(gè) → 繼續(xù)下一步匹配
策略2:按@Primary匹配 - 優(yōu)先級
@Repository
@Primary // 標(biāo)記為首選
public class UserDaoMysqlImpl implements UserDao { }
@Repository
public class UserDaoMongoImpl implements UserDao { }
@Service
public class UserService {
@Autowired
private UserDao userDao; // 自動(dòng)選擇@Primary標(biāo)記的Bean
}策略3:按@Qualifier匹配 - 明確指定
@Service
public class UserService {
@Autowired
@Qualifier("userDaoMongoImpl") // 明確指定使用哪個(gè)Bean
private UserDao userDao;
}
策略4:按字段名稱匹配 - 輔助
@Service
public class UserService {
@Autowired
private UserDao userDaoMysqlImpl; // 字段名與Bean名稱一致
}
策略5:注入集合 - 獲取所有實(shí)現(xiàn)
@Service
public class UserService {
// 注入所有UserDao類型的Bean
@Autowired
private List<UserDao> userDaoList;
// 注入所有UserDao類型的Bean到Map,key為Bean名稱
@Autowired
private Map<String, UserDao> userDaoMap;
}
匹配優(yōu)先級:
@Qualifier(最高優(yōu)先級)
↓
@Primary
↓
字段名稱匹配
↓
類型匹配(默認(rèn))4.5 處理多個(gè)候選Bean
當(dāng)容器中有多個(gè)相同類型的Bean時(shí):
// 有兩個(gè)UserDao的實(shí)現(xiàn)
@Repository
public class UserDaoMysqlImpl implements UserDao { }
@Repository
public class UserDaoMongoImpl implements UserDao { }
// 注入時(shí)會(huì)報(bào)錯(cuò)
@Service
public class UserService {
@Autowired
private UserDao userDao; // 錯(cuò)誤:找到2個(gè)候選Bean
}
解決方案總結(jié):
| 方案 | 代碼示例 | 適用場景 |
|---|---|---|
| @Qualifier | @Qualifier("userDaoMysqlImpl") | 明確知道要用哪個(gè)Bean |
| @Primary | 在Bean上加@Primary | 有一個(gè)默認(rèn)首選實(shí)現(xiàn) |
| 字段名匹配 | private UserDao userDaoMysqlImpl | 簡單場景 |
| 注入List/Map | List<UserDao> userDaoList | 需要使用所有實(shí)現(xiàn) |
4.6 可選依賴的處理
方式1:使用required=false
@Service
public class UserService {
@Autowired(required = false) // 找不到Bean不報(bào)錯(cuò),注入null
private CacheService cacheService;
public User getUser(Long id) {
if (cacheService != null) {
User cached = cacheService.get(id);
if (cached != null) return cached;
}
return userDao.findById(id);
}
}方式2:使用Optional(更優(yōu)雅)
@Service
public class UserService {
@Autowired
private Optional<CacheService> cacheService;
public User getUser(Long id) {
return cacheService
.map(cache -> cache.get(id))
.orElseGet(() -> userDao.findById(id));
}
}4.7 自動(dòng)裝配的注意事項(xiàng)
注意1:理解裝配時(shí)機(jī)
自動(dòng)裝配發(fā)生在Bean的屬性注入階段,在構(gòu)造函數(shù)之后:
@Service
public class UserService {
@Autowired
private UserDao userDao;
public UserService() {
// 錯(cuò)誤:此時(shí)userDao還未注入,為null
// userDao.findAll();
}
@PostConstruct
public void init() {
// 正確:此時(shí)userDao已經(jīng)注入
userDao.findAll();
}
}注意2:避免循環(huán)依賴
// 不推薦:循環(huán)依賴
@Service
public class UserService {
@Autowired
private OrderService orderService;
}
@Service
public class OrderService {
@Autowired
private UserService userService;
}
// 推薦:重新設(shè)計(jì),單向依賴
@Service
public class OrderService {
@Autowired
private UserService userService; // 單向依賴
}4.8 自動(dòng)裝配與SpringBoot自動(dòng)配置的區(qū)別
很多人容易混淆這兩個(gè)概念:
自動(dòng)裝配(Auto-Wiring):
- Spring的核心功能
- 負(fù)責(zé)注入依賴
- 通過@Autowired實(shí)現(xiàn)
- Bean已存在,只是注入
自動(dòng)配置(Auto-Configuration):
- SpringBoot的特性
- 負(fù)責(zé)創(chuàng)建Bean
- 通過@EnableAutoConfiguration實(shí)現(xiàn)
- 根據(jù)條件自動(dòng)創(chuàng)建Bean
關(guān)系:
SpringBoot自動(dòng)配置 → 創(chuàng)建Bean
↓
Spring自動(dòng)裝配 → 注入Bean舉例:
// SpringBoot自動(dòng)配置:自動(dòng)創(chuàng)建DataSource Bean
@Configuration
@ConditionalOnClass(DataSource.class)
public class DataSourceAutoConfiguration {
@Bean
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
}
// 自動(dòng)裝配:自動(dòng)注入DataSource依賴
@Service
public class UserService {
@Autowired // 自動(dòng)裝配
private DataSource dataSource; // 這個(gè)Bean是自動(dòng)配置創(chuàng)建的
}五、其他注入注解對比
5.1 @Resource(JSR-250規(guī)范)
@Service
public class UserService {
@Resource // 默認(rèn)按名稱裝配
private UserDao userDao;
@Resource(name = "userDaoMysqlImpl") // 指定Bean名稱
private UserDao userDao2;
}@Autowired vs @Resource:
| 特性 | @Autowired | @Resource |
|---|---|---|
| 來源 | Spring框架 | Java EE規(guī)范 |
| 默認(rèn)裝配方式 | 按類型(byType) | 按名稱(byName) |
| 指定Bean | 配合@Qualifier | 使用name屬性 |
| 支持required | 支持 | 不支持 |
| 支持構(gòu)造器注入 | 支持 | 不支持 |
建議: 優(yōu)先使用@Autowired,功能更強(qiáng)大,是Spring原生注解。
5.2 @Inject(JSR-330)
來自Java依賴注入規(guī)范:
@Service
public class UserService {
@Inject // 功能類似@Autowired
private UserDao userDao;
}需要額外引入依賴:
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>@Inject vs @Autowired:
| 特性 | @Autowired | @Inject |
|---|---|---|
| 來源 | Spring | JSR-330 |
| 是否支持required | 支持 | 不支持 |
| 配合注解 | @Qualifier | @Named |
建議: 除非需要跨框架兼容,否則使用 @Autowired 即可。
5.3 @Value(注入配置值)
用于注入配置文件中的值,這是學(xué)習(xí)SpringBoot配置的基礎(chǔ):
@Service
public class UserService {
@Value("${app.name}") // 從配置文件讀取
private String appName;
@Value("${app.max-users:100}") // 指定默認(rèn)值100
private int maxUsers;
@Value("#{systemProperties['user.home']}") // SpEL表達(dá)式
private String userHome;
}配置文件(application.properties):
app.name=我的應(yīng)用 app.max-users=500
六、常見問題與解決方案
問題1:循環(huán)依賴
什么是循環(huán)依賴?
兩個(gè)或多個(gè)Bean相互依賴,形成閉環(huán):
@Service
public class UserService {
@Autowired
private OrderService orderService; // UserService依賴OrderService
}
@Service
public class OrderService {
@Autowired
private UserService userService; // OrderService依賴UserService
}Spring如何解決?
Spring通過三級緩存機(jī)制解決單例Bean的字段注入循環(huán)依賴:
- 一級緩存:存放完整的Bean實(shí)例
- 二級緩存:存放早期的Bean引用(已實(shí)例化但未完成依賴注入)
- 三級緩存:存放Bean工廠(用于創(chuàng)建代理對象)
無法解決的情況:構(gòu)造器循環(huán)依賴
@Service
public class UserService {
private final OrderService orderService;
public UserService(OrderService orderService) {
this.orderService = orderService;
}
}
@Service
public class OrderService {
private final UserService userService;
public OrderService(UserService userService) {
this.userService = userService; // 報(bào)錯(cuò)!無法解決
}
}解決方法:
- 重新設(shè)計(jì),消除循環(huán)依賴(最佳方案)
- 使用@Lazy延遲加載
@Service
public class UserService {
private final OrderService orderService;
public UserService(@Lazy OrderService orderService) {
this.orderService = orderService; // 注入代理對象,延遲初始化
}
}- 改用字段注入(不推薦,只是權(quán)宜之計(jì))
問題2:注入的Bean為null
常見原因:
原因1:類沒有被Spring管理
// 錯(cuò)誤:忘記加@Service注解
public class UserService {
@Autowired
private UserDao userDao; // 注入失敗,為null
}
// 正確:添加@Service注解
@Service
public class UserService {
@Autowired
private UserDao userDao; // 正常注入
}原因2:包掃描路徑不對
@SpringBootApplication
@ComponentScan("com.example.service") // 只掃描service包
public class Application {
// UserDao在com.example.dao包下,掃描不到
}
// 正確:掃描父包
@SpringBootApplication
@ComponentScan("com.example") // 掃描整個(gè)com.example包
public class Application { }原因3:直接new對象,不從Spring容器獲取
// 錯(cuò)誤:直接new的對象,Spring無法注入依賴
UserService service = new UserService();
service.getUser(1L); // userDao為null,報(bào)NPE
// 正確:從Spring容器獲取
@RestController
public class UserController {
@Autowired
private UserService userService; // 從容器獲取,依賴已注入
@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
return userService.getUser(id); // 正常使用
}
}原因4:在靜態(tài)字段上使用@Autowired
// 錯(cuò)誤:無法注入靜態(tài)字段
@Service
public class UserService {
@Autowired
private static UserDao userDao; // 無法注入
}
// 正確:使用非靜態(tài)字段
@Service
public class UserService {
@Autowired
private UserDao userDao; // 正常注入
}問題3:注入時(shí)機(jī)問題
錯(cuò)誤示例:在構(gòu)造函數(shù)中使用依賴
@Service
public class UserService {
@Autowired
private UserDao userDao;
private List<User> cache;
// 錯(cuò)誤:構(gòu)造函數(shù)執(zhí)行時(shí),userDao還未注入
public UserService() {
this.cache = userDao.findAll(); // NPE!
}
}正確做法:使用@PostConstruct
@Service
public class UserService {
@Autowired
private UserDao userDao;
private List<User> cache;
@PostConstruct // 在依賴注入完成后執(zhí)行
public void init() {
this.cache = userDao.findAll(); // 正確
}
}Bean的生命周期:
1. 實(shí)例化(執(zhí)行構(gòu)造函數(shù)) ↓ 2. 依賴注入(@Autowired生效) ↓ 3. 初始化前(@PostConstruct執(zhí)行) ↓ 4. 初始化(InitializingBean.afterPropertiesSet()) ↓ 5. Bean可用 ↓ 6. 銷毀前(@PreDestroy執(zhí)行) ↓ 7. 銷毀(DisposableBean.destroy())
七、最佳實(shí)踐
1. 優(yōu)先使用構(gòu)造器注入
// 推薦
@Service
public class UserService {
private final UserDao userDao; // final保證線程安全
public UserService(UserDao userDao) {
this.userDao = userDao;
}
}
//或者可以這樣寫,一個(gè)意思
@Service
@@RequiredArgsConstructor
public class UserService {
private final UserDao userDao;
}2. 使用final保證不可變性
@Service
public class UserService {
private final UserDao userDao; // final保證線程安全
public UserService(UserDao userDao) {
this.userDao = userDao;
}
}3. 多個(gè)相同類型Bean時(shí)明確指定
@Service
public class UserService {
private final UserDao userDao;
public UserService(@Qualifier("userDaoMysqlImpl") UserDao userDao) {
this.userDao = userDao;
}
}4. 可選依賴使用Optional
@Service
public class UserService {
@Autowired
private Optional<CacheService> cacheService;
}
5. 避免循環(huán)依賴
重新設(shè)計(jì)類的職責(zé),消除循環(huán)依賴。如果實(shí)在無法避免,使用@Lazy。
八、面試高頻問題
問題1:@Autowired和@Resource的區(qū)別?
答案:
| 維度 | @Autowired | @Resource |
|---|---|---|
| 來源 | Spring框架 | Java EE規(guī)范(JSR-250) |
| 裝配方式 | 默認(rèn)byType,可配合@Qualifier按名稱 | 默認(rèn)byName,可通過name屬性指定 |
| 作用范圍 | 字段、構(gòu)造器、方法、參數(shù) | 字段、方法 |
| required屬性 | 支持 | 不支持 |
| 推薦使用 | Spring項(xiàng)目推薦 | 需要跨框架兼容時(shí)使用 |
問題2:什么是循環(huán)依賴?Spring如何解決?
答案:
循環(huán)依賴是指兩個(gè)或多個(gè)Bean相互依賴,形成閉環(huán)。
Spring通過三級緩存解決單例Bean的循環(huán)依賴:
- 一級緩存:完整的Bean實(shí)例
- 二級緩存:早期的Bean引用(未完成依賴注入)
- 三級緩存:Bean工廠(用于創(chuàng)建代理對象)
注意: 構(gòu)造器循環(huán)依賴無法解決,需要使用@Lazy或重新設(shè)計(jì)。
問題3:為什么推薦使用構(gòu)造器注入?
答案:
- 依賴明確:所有依賴在構(gòu)造器中聲明,一目了然
- 不可變性:可以使用final修飾,保證線程安全
- 便于測試:可以直接new對象傳參,無需Spring容器
- 避免NPE:如果依賴為null,在創(chuàng)建時(shí)就會(huì)失敗
- 強(qiáng)制完整性:必須提供所有依賴才能創(chuàng)建對象
問題4:@Autowired的裝配流程是什么?
答案:
- Bean實(shí)例化(調(diào)用構(gòu)造函數(shù))
AutowiredAnnotationBeanPostProcessor掃描@Autowired注解- 解析依賴類型
- 在容器中查找匹配的Bean:
- 先按類型查找
- 如果有多個(gè),按@Primary篩選
- 如果還有多個(gè),按@Qualifier篩選
- 如果還有多個(gè),按字段名稱匹配
- 通過反射注入依賴
- 調(diào)用@PostConstruct方法
- Bean初始化完成
問題5:如何解決多個(gè)相同類型Bean的注入問題?
答案:
- 使用
@Qualifier指定Bean名稱 - 使用
@Primary標(biāo)記主要Bean - 使用字段名稱匹配Bean名稱
- 注入
List<T>或Map<String, T>獲取所有Bean - 使用
@Resource(name="beanName")按名稱注入
問題6:依賴注入和工廠模式有什么區(qū)別?
答案:
工廠模式:
- 對象自己通過工廠獲取依賴
- 對象知道工廠的存在
- 耦合度較高
依賴注入:
- 容器主動(dòng)將依賴注入到對象
- 對象不知道容器的存在
- 完全解耦
依賴注入是控制反轉(zhuǎn)的一種實(shí)現(xiàn)方式,比工廠模式更徹底地解耦。
九、與SpringBoot的關(guān)系
理解了Spring的依賴注入,你就能更好地理解SpringBoot的自動(dòng)配置原理。
SpringBoot如何簡化依賴注入?
- 自動(dòng)包掃描
@SpringBootApplication // 包含@ComponentScan,自動(dòng)掃描當(dāng)前包及子包
//目標(biāo)類標(biāo)注 @Component 或其衍生注解,且類處于啟動(dòng)類所在包/子包下,SpringBoot 就會(huì)自動(dòng)掃描并將其注冊為 Bean 實(shí)例。
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
- 自動(dòng)配置Bean
SpringBoot會(huì)自動(dòng)配置常用的Bean(如DataSource、JdbcTemplate等),你只需注入使用:
@Service
public class UserService {
@Autowired
private JdbcTemplate jdbcTemplate; // SpringBoot自動(dòng)配置,直接注入
}- Starter依賴
引入starter依賴,相關(guān)Bean自動(dòng)配置:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
@Service
public class UserService {
@Autowired
private EntityManager entityManager; // 自動(dòng)配置,直接注入
}
本文是學(xué)習(xí)SpringBoot的基礎(chǔ):
- SpringBoot的自動(dòng)配置本質(zhì)上就是自動(dòng)創(chuàng)建Bean并注入依賴
- 理解了依賴注入,才能理解@ConditionalOnBean等條件注解
- 理解了依賴注入,才能理解Starter的工作原理
下一步學(xué)習(xí): 閱讀《SpringBoot自動(dòng)配置原理深度解析》,理解SpringBoot如何自動(dòng)創(chuàng)建和配置Bean。
十、推薦資源
官方文檔
- Spring Framework Reference:https://docs.spring.io/spring-framework/docs/current/reference/html/
- Spring IoC Container:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans
源碼閱讀
- Spring Framework:https://github.com/spring-projects/spring-framework
- 重點(diǎn)類:
AutowiredAnnotationBeanPostProcessorDefaultListableBeanFactoryAbstractAutowireCapableBeanFactory
十一、總結(jié)
核心要點(diǎn):
- Bean是由Spring容器管理的對象
- 依賴是一個(gè)對象需要使用另一個(gè)對象
- 依賴注入是Spring把依賴對象注入給需要的對象
- 控制反轉(zhuǎn)是把對象創(chuàng)建和管理的控制權(quán)交給Spring
- @Autowired默認(rèn)按類型裝配,可配合@Qualifier按名稱
- 構(gòu)造器注入是最佳實(shí)踐,優(yōu)于字段注入
- 循環(huán)依賴通過三級緩存解決,但構(gòu)造器循環(huán)依賴無法解決
學(xué)習(xí)路線:
Spring依賴注入(本文)
↓
SpringBoot自動(dòng)配置原理
↓
自定義Starter開發(fā)
↓
Spring AOP原理
↓
Spring事務(wù)管理給讀者的建議:
- 動(dòng)手實(shí)踐:寫代碼,打斷點(diǎn)調(diào)試
- 閱讀源碼:看看AutowiredAnnotationBeanPostProcessor的實(shí)現(xiàn)
- 理解原理:知其然更要知其所以然
- 實(shí)際應(yīng)用:在項(xiàng)目中使用最佳實(shí)踐
到此這篇關(guān)于Spring依賴注入與自動(dòng)裝配原理解析的文章就介紹到這了,更多相關(guān)spring依賴注入與自動(dòng)裝配內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決springboot mapper注入報(bào)紅問題
這篇文章主要介紹了解決springboot mapper注入報(bào)紅問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11
Spring BeanFactory和FactoryBean有哪些區(qū)別
這篇文章主要介紹了Spring BeanFactory 與 FactoryBean 的區(qū)別詳情,BeanFactory 和 FactoryBean 的區(qū)別卻是一個(gè)很重要的知識點(diǎn),在本文中將結(jié)合源碼進(jìn)行分析講解,需要的小伙伴可以參考一下2023-02-02
Javaweb動(dòng)態(tài)開發(fā)最重要的Servlet詳解
動(dòng)態(tài)web的核心是Servlet,由tomcat解析并執(zhí)行,本質(zhì)是Java中的一個(gè)類(面向?qū)ο螅┻@個(gè)類的功能十分強(qiáng)大幾乎可以完成全部功能,在Java規(guī)范中只有Servlet實(shí)現(xiàn)類實(shí)例化的對象才能被瀏覽器訪問,所以掌握Servlet具有重要意義2022-08-08
Java IO流之原理分類與節(jié)點(diǎn)流文件操作詳解
流(Stream)是指一連串的數(shù)據(jù)(字符或字節(jié)),是以先進(jìn)先出的方式發(fā)送信息的通道,數(shù)據(jù)源發(fā)送的數(shù)據(jù)經(jīng)過這個(gè)通道到達(dá)目的地,按流向區(qū)分為輸入流和輸出流2021-10-10
在?Java?中將Object?轉(zhuǎn)換為?Int的四種方法
這篇文章主要介紹了在Java中如何將?Object?轉(zhuǎn)換為Int,本文研究了在?Java中將Object轉(zhuǎn)換為int的四種不同方法,結(jié)合示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-05-05
SpringBoot整合XxlJob分布式任務(wù)調(diào)度平臺
xxl-job是一個(gè)開源的分布式定時(shí)任務(wù)框架,它可以與其他微服務(wù)組件一起構(gòu)成微服務(wù)集群。它的調(diào)度中心(xxl-job)和執(zhí)行器(自己的springboot項(xiàng)目中有@XxlJob("定時(shí)任務(wù)名稱")的方法)是相互分離,分開部署的,兩者通過HTTP協(xié)議進(jìn)行通信2023-02-02
springMVC返回Http響應(yīng)的實(shí)現(xiàn)
本文主要介紹了在Spring Boot中使用@Controller、@ResponseBody和@RestController注解進(jìn)行HTTP響應(yīng)返回的方法,具有一定的參考價(jià)值,感興趣的可以了解一下2025-03-03

