最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring依賴注入與自動(dòng)裝配原理解析

 更新時(shí)間:2026年02月09日 11:26:31   作者:識(shí)君啊  
本文講解Spring依賴注入核心知識(shí):闡釋 Bean、IoC 等概念,介紹三種注入方式(推薦構(gòu)造器注入),解析 @Autowired 自動(dòng)裝配原理、注解差異及循環(huán)依賴等問(wèn)題的解決辦法,感興趣的朋友跟隨小編一起看看吧

寫在前面

如果你想深入學(xué)習(xí)SpringBoot的自動(dòng)配置原理,那么本文是你必須掌握的基礎(chǔ)知識(shí)。很多人在使用SpringBoot時(shí),只知道用@Autowired注入依賴,卻不知道背后的原理。

本文將從最基礎(chǔ)的概念講起,帶你徹底理解Spring的依賴注入機(jī)制。無(wú)論你是剛接觸Spring的新手,還是想復(fù)習(xí)鞏固的老手,都能從本文獲益。

適合人群: Spring初學(xué)者、準(zhǔn)備深入學(xué)習(xí)SpringBoot的開發(fā)者、需要復(fù)習(xí)基礎(chǔ)的工程師

一、核心概念:什么是Bean、依賴和注入

在學(xué)習(xí)依賴注入之前,我們必須先理解三個(gè)基本概念。

1.1 什么是Bean?

Bean就是由Spring容器管理的對(duì)象。

在傳統(tǒng)Java開發(fā)中,我們這樣創(chuàng)建對(duì)象:

UserService userService = new UserService();

而在Spring中,我們不需要自己new對(duì)象,而是讓Spring容器幫我們創(chuàng)建和管理:

@Service  // 告訴Spring:這是一個(gè)Bean,請(qǐng)幫我管理。@Service是@Component的衍生注解
public class UserService {
    // ...
}

形象的比喻:

  • 傳統(tǒng)方式:你需要什么對(duì)象,自己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è)對(duì)象需要使用另一個(gè)對(duì)象來(lái)完成工作。

舉個(gè)例子:

public class UserService {
    private UserDao userDao;  // UserService依賴UserDao
    public User getUser(Long id) {
        return userDao.findById(id);  // 需要用到UserDao
    }
}

在這個(gè)例子中:

  • UserService 需要 UserDao 才能完成查詢用戶的功能
  • 我們說(shuō):UserService 依賴 UserDao

生活中的例子:

  • 你要開車上班,你依賴汽車
  • 你要做飯,你依賴廚具
  • 你要寫代碼,你依賴電腦

1.3 什么是注入依賴?

注入依賴就是把一個(gè)對(duì)象需要的其他對(duì)象"送"給它。

傳統(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:請(qǐng)把UserDao注入給我
    private UserDao userDao;
}

形象的比喻:

  • 傳統(tǒng)方式:你要喝咖啡,自己買豆子、磨豆、煮咖啡(自己創(chuàng)建依賴)
  • 依賴注入:你要喝咖啡,告訴咖啡店,咖啡店做好送給你(Spring注入依賴)

依賴注入的好處:

  1. 解耦:UserService不需要知道UserDao的具體實(shí)現(xiàn)
  2. 易測(cè)試:可以輕松替換為Mock對(duì)象進(jìn)行測(cè)試
  3. 易維護(hù):要換實(shí)現(xiàn),不需要修改UserService代碼
  4. 統(tǒng)一管理:所有對(duì)象由Spring容器統(tǒng)一創(chuàng)建和管理

二、控制反轉(zhuǎn)(IoC):理解Spring的核心思想

2.1 什么是控制反轉(zhuǎn)?

控制反轉(zhuǎn)(Inversion of Control,IoC)是一種設(shè)計(jì)思想:把對(duì)象創(chuàng)建和管理的控制權(quán)交給框架。

簡(jiǎn)單來(lái)說(shuō):就相當(dāng)于框架幫你創(chuàng)建對(duì)象,只創(chuàng)建一次,那個(gè)類調(diào)用就把對(duì)象注入給誰(shuí)

好處

  1. 徹底解放雙手:不用再手動(dòng)new對(duì)象、手動(dòng)傳依賴
  2. 統(tǒng)一管理對(duì)象:單例復(fù)用 + 生命周期全托管
  • 單例復(fù)用:框架創(chuàng)建的 Bean 默認(rèn)是單例(只創(chuàng)建 1 次),所有需要這個(gè)對(duì)象的類,注入的都是同一個(gè)實(shí)例,避免重復(fù)創(chuàng)建對(duì)象浪費(fèi)內(nèi)存(比如一個(gè)UserDao對(duì)象,所有Service都用同一個(gè),不用每次都new);
  • 生命周期托管:對(duì)象的 “創(chuàng)建→初始化→使用→銷毀” 全由框架控制,你可以通過(guò)注解(如@PostConstruct初始化、@PreDestroy銷毀)自定義流程,不用手動(dòng)管理對(duì)象的生滅。
  1. 降低代碼耦合:類之間解耦,項(xiàng)目易維護(hù)、易擴(kuò)展

傳統(tǒng)方式: 你控制一切

public class UserService {
    private UserDao userDao;
    public UserService() {
        // 你決定創(chuàng)建什么對(duì)象
        this.userDao = new UserDaoImpl();
    }
}

IoC方式: Spring控制一切

@Service
public class UserService {
    @Autowired
    private UserDao userDao;  // Spring決定注入什么對(duì)象
}

"反轉(zhuǎn)"體現(xiàn)在哪里?

維度傳統(tǒng)方式IoC方式
誰(shuí)創(chuàng)建對(duì)象你自己newSpring容器創(chuàng)建
誰(shuí)管理對(duì)象你自己管理Spring容器管理
誰(shuí)決定依賴你在代碼中寫死Spring根據(jù)配置決定

核心思想: 不要打電話給我們,我們會(huì)打電話給你(Don’t call us, we’ll call you)

2.2 IoC容器的作用

Spring的IoC容器(ApplicationContext)就像一個(gè)對(duì)象工廠,負(fù)責(zé):

  1. 創(chuàng)建對(duì)象:根據(jù)配置創(chuàng)建Bean
  2. 管理生命周期:從創(chuàng)建到銷毀
  3. 注入依賴:自動(dòng)裝配Bean之間的依賴關(guān)系
  4. 提供服務(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支持三種依賴注入方式,每種都有各自的適用場(chǎng)景。

方式1:字段注入(最常見但不推薦)

@Service
public class UserService {
    @Autowired
    private UserDao userDao;
    @Autowired
    private EmailService emailService;
}

優(yōu)點(diǎn): 代碼簡(jiǎn)潔,寫起來(lái)方便

缺點(diǎn):

  • 無(wú)法注入final字段(不能保證不可變性)
  • 不利于單元測(cè)試(必須啟動(dòng)Spring容器或使用反射)
  • 違反了面向?qū)ο蟮姆庋b原則
  • 依賴關(guān)系不明確

方式2:構(gòu)造器注入(強(qiáng)烈推薦)

@Service
//@RequiredArgsConstructor      
//Lombok注解,寫了這個(gè)就不用寫構(gòu)造器了,但是要加入Lombok依賴。(依賴在博主之前的關(guān)于Lombok的文章中有提到過(guò))
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,保證不可變性和線程安全
  • 便于單元測(cè)試(直接new對(duì)象傳參,無(wú)需Spring容器)
  • 依賴關(guān)系一目了然
  • 如果依賴過(guò)多,構(gòu)造器參數(shù)會(huì)很長(zhǎng),提醒你類職責(zé)可能過(guò)重

這是Spring官方推薦的方式!

方式3:Setter方法注入

@Service
public class UserService {
    private UserDao userDao;
    @Autowired
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}

優(yōu)點(diǎn): 可以在對(duì)象創(chuàng)建后重新注入依賴(適合可選依賴)

缺點(diǎn): 依賴可能為null,不夠安全

三種方式對(duì)比:

特性字段注入構(gòu)造器注入Setter注入
代碼簡(jiǎn)潔度最簡(jiǎn)潔中等較繁瑣
是否支持final
測(cè)試友好度中等
依賴明確度不明確非常明確較明確
官方推薦度不推薦強(qiáng)烈推薦適合可選依賴

四、@Autowired與自動(dòng)裝配詳解

4.1 什么是自動(dòng)裝配?

自動(dòng)裝配(Auto-Wiring)是Spring自動(dòng)為Bean注入依賴的機(jī)制。

簡(jiǎn)單來(lái)說(shuō):你只需要告訴Spring"我需要這個(gè)依賴"(通過(guò)@Autowired注解),Spring會(huì)自動(dòng)幫你找到合適的Bean并注入進(jìn)來(lái)。

傳統(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  // 注解方式,更簡(jiǎn)潔
    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. 通過(guò)反射注入依賴
   ↓
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)先級(jí)

@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)先級(jí):

@Qualifier(最高優(yōu)先級(jí))
    ↓
@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é):

方案代碼示例適用場(chǎng)景
@Qualifier@Qualifier("userDaoMysqlImpl")明確知道要用哪個(gè)Bean
@Primary在Bean上加@Primary有一個(gè)默認(rèn)首選實(shí)現(xiàn)
字段名匹配private UserDao userDaoMysqlImpl簡(jiǎn)單場(chǎng)景
注入List/MapList<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é)注入依賴
  • 通過(guò)@Autowired實(shí)現(xiàn)
  • Bean已存在,只是注入

自動(dòng)配置(Auto-Configuration):

  • SpringBoot的特性
  • 負(fù)責(zé)創(chuàng)建Bean
  • 通過(guò)@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)建的
}

五、其他注入注解對(duì)比

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
來(lái)源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)

來(lái)自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
來(lái)源SpringJSR-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

六、常見問(wèn)題與解決方案

問(wèn)題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通過(guò)三級(jí)緩存機(jī)制解決單例Bean的字段注入循環(huán)依賴:

  1. 一級(jí)緩存:存放完整的Bean實(shí)例
  2. 二級(jí)緩存:存放早期的Bean引用(已實(shí)例化但未完成依賴注入)
  3. 三級(jí)緩存:存放Bean工廠(用于創(chuàng)建代理對(duì)象)

無(wú)法解決的情況:構(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ò)!無(wú)法解決
    }
}

解決方法:

  1. 重新設(shè)計(jì),消除循環(huán)依賴(最佳方案)
  2. 使用@Lazy延遲加載
@Service
public class UserService {
    private final OrderService orderService;
    public UserService(@Lazy OrderService orderService) {
        this.orderService = orderService;  // 注入代理對(duì)象,延遲初始化
    }
}
  1. 改用字段注入(不推薦,只是權(quán)宜之計(jì))

問(wèn)題2:注入的Bean為null

常見原因:

原因1:類沒(méi)有被Spring管理

// 錯(cuò)誤:忘記加@Service注解
public class UserService {
    @Autowired
    private UserDao userDao;  // 注入失敗,為null
}
// 正確:添加@Service注解
@Service
public class UserService {
    @Autowired
    private UserDao userDao;  // 正常注入
}

原因2:包掃描路徑不對(duì)

@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對(duì)象,不從Spring容器獲取

// 錯(cuò)誤:直接new的對(duì)象,Spring無(wú)法注入依賴
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ò)誤:無(wú)法注入靜態(tài)字段
@Service
public class UserService {
    @Autowired
    private static UserDao userDao;  // 無(wú)法注入
}
// 正確:使用非靜態(tài)字段
@Service
public class UserService {
    @Autowired
    private UserDao userDao;  // 正常注入
}

問(wèn)題3:注入時(shí)機(jī)問(wèn)題

錯(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í)在無(wú)法避免,使用@Lazy。

八、面試高頻問(wèn)題

問(wèn)題1:@Autowired和@Resource的區(qū)別?

答案:

維度@Autowired@Resource
來(lái)源Spring框架Java EE規(guī)范(JSR-250)
裝配方式默認(rèn)byType,可配合@Qualifier按名稱默認(rèn)byName,可通過(guò)name屬性指定
作用范圍字段、構(gòu)造器、方法、參數(shù)字段、方法
required屬性支持不支持
推薦使用Spring項(xiàng)目推薦需要跨框架兼容時(shí)使用

問(wèn)題2:什么是循環(huán)依賴?Spring如何解決?

答案:

循環(huán)依賴是指兩個(gè)或多個(gè)Bean相互依賴,形成閉環(huán)。

Spring通過(guò)三級(jí)緩存解決單例Bean的循環(huán)依賴:

  1. 一級(jí)緩存:完整的Bean實(shí)例
  2. 二級(jí)緩存:早期的Bean引用(未完成依賴注入)
  3. 三級(jí)緩存:Bean工廠(用于創(chuàng)建代理對(duì)象)

注意: 構(gòu)造器循環(huán)依賴無(wú)法解決,需要使用@Lazy或重新設(shè)計(jì)。

問(wèn)題3:為什么推薦使用構(gòu)造器注入?

答案:

  1. 依賴明確:所有依賴在構(gòu)造器中聲明,一目了然
  2. 不可變性:可以使用final修飾,保證線程安全
  3. 便于測(cè)試:可以直接new對(duì)象傳參,無(wú)需Spring容器
  4. 避免NPE:如果依賴為null,在創(chuàng)建時(shí)就會(huì)失敗
  5. 強(qiáng)制完整性:必須提供所有依賴才能創(chuàng)建對(duì)象

問(wèn)題4:@Autowired的裝配流程是什么?

答案:

  1. Bean實(shí)例化(調(diào)用構(gòu)造函數(shù))
  2. AutowiredAnnotationBeanPostProcessor 掃描@Autowired注解
  3. 解析依賴類型
  4. 在容器中查找匹配的Bean:
    • 先按類型查找
    • 如果有多個(gè),按@Primary篩選
    • 如果還有多個(gè),按@Qualifier篩選
    • 如果還有多個(gè),按字段名稱匹配
  5. 通過(guò)反射注入依賴
  6. 調(diào)用@PostConstruct方法
  7. Bean初始化完成

問(wèn)題5:如何解決多個(gè)相同類型Bean的注入問(wèn)題?

答案:

  1. 使用 @Qualifier 指定Bean名稱
  2. 使用 @Primary 標(biāo)記主要Bean
  3. 使用字段名稱匹配Bean名稱
  4. 注入 List<T>Map<String, T> 獲取所有Bean
  5. 使用 @Resource(name="beanName") 按名稱注入

問(wèn)題6:依賴注入和工廠模式有什么區(qū)別?

答案:

工廠模式:

  • 對(duì)象自己通過(guò)工廠獲取依賴
  • 對(duì)象知道工廠的存在
  • 耦合度較高

依賴注入:

  • 容器主動(dòng)將依賴注入到對(duì)象
  • 對(duì)象不知道容器的存在
  • 完全解耦

依賴注入是控制反轉(zhuǎn)的一種實(shí)現(xiàn)方式,比工廠模式更徹底地解耦。

九、與SpringBoot的關(guān)系

理解了Spring的依賴注入,你就能更好地理解SpringBoot的自動(dòng)配置原理。

SpringBoot如何簡(jiǎn)化依賴注入?

  1. 自動(dòng)包掃描
@SpringBootApplication  // 包含@ComponentScan,自動(dòng)掃描當(dāng)前包及子包
//目標(biāo)類標(biāo)注 @Component 或其衍生注解,且類處于啟動(dòng)類所在包/子包下,SpringBoot 就會(huì)自動(dòng)掃描并將其注冊(cè)為 Bean 實(shí)例。
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
  1. 自動(dòng)配置Bean

SpringBoot會(huì)自動(dòng)配置常用的Bean(如DataSource、JdbcTemplate等),你只需注入使用:

@Service
public class UserService {
    @Autowired
    private JdbcTemplate jdbcTemplate;  // SpringBoot自動(dòng)配置,直接注入
}
  1. 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)類:
    • AutowiredAnnotationBeanPostProcessor
    • DefaultListableBeanFactory
    • AbstractAutowireCapableBeanFactory

十一、總結(jié)

核心要點(diǎn):

  1. Bean是由Spring容器管理的對(duì)象
  2. 依賴是一個(gè)對(duì)象需要使用另一個(gè)對(duì)象
  3. 依賴注入是Spring把依賴對(duì)象注入給需要的對(duì)象
  4. 控制反轉(zhuǎn)是把對(duì)象創(chuàng)建和管理的控制權(quán)交給Spring
  5. @Autowired默認(rèn)按類型裝配,可配合@Qualifier按名稱
  6. 構(gòu)造器注入是最佳實(shí)踐,優(yōu)于字段注入
  7. 循環(huán)依賴通過(guò)三級(jí)緩存解決,但構(gòu)造器循環(huán)依賴無(wú)法解決

學(xué)習(xí)路線:

Spring依賴注入(本文)
    ↓
SpringBoot自動(dòng)配置原理
    ↓
自定義Starter開發(fā)
    ↓
Spring AOP原理
    ↓
Spring事務(wù)管理

給讀者的建議:

  1. 動(dòng)手實(shí)踐:寫代碼,打斷點(diǎn)調(diào)試
  2. 閱讀源碼:看看AutowiredAnnotationBeanPostProcessor的實(shí)現(xiàn)
  3. 理解原理:知其然更要知其所以然
  4. 實(shí)際應(yīng)用:在項(xiàng)目中使用最佳實(shí)踐

到此這篇關(guān)于Spring依賴注入與自動(dòng)裝配原理解析的文章就介紹到這了,更多相關(guān)spring依賴注入與自動(dòng)裝配內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 新手入門Jvm--Jvm垃圾回收

    新手入門Jvm--Jvm垃圾回收

    JVM是Java Virtual Machine(Java虛擬機(jī))的縮寫,JVM是一種用于計(jì)算設(shè)備的規(guī)范,它是一個(gè)虛構(gòu)出來(lái)的計(jì)算機(jī),是通過(guò)在實(shí)際的計(jì)算機(jī)上仿真模擬各種計(jì)算機(jī)功能來(lái)實(shí)現(xiàn)的
    2021-06-06
  • SpringMvc請(qǐng)求處理參數(shù)?和?響應(yīng)數(shù)據(jù)處理的示例詳解

    SpringMvc請(qǐng)求處理參數(shù)?和?響應(yīng)數(shù)據(jù)處理的示例詳解

    這篇文章主要介紹了SpringMvc請(qǐng)求處理參數(shù)和響應(yīng)數(shù)據(jù)處理,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-09-09
  • Java List中數(shù)據(jù)的去重

    Java List中數(shù)據(jù)的去重

    今天小編就為大家分享一篇關(guān)于Java List中數(shù)據(jù)的去重,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2019-01-01
  • SpringBoot啟動(dòng)失敗的解決方法:A component required a bean of type ‘xxxxxxx‘ that could not be found.

    SpringBoot啟動(dòng)失敗的解決方法:A component required a&nb

    這篇文章主要介紹了解決SpringBoot啟動(dòng)失敗:A component required a bean of type ‘xxxxxxx‘ that could not be found.,目前解決方法有兩種,一種是不注入bean的方式,另一種是使用@Component的方式,本文給大家詳細(xì)講解,需要的朋友可以參考下
    2023-02-02
  • Spring Security架構(gòu)以及源碼詳析

    Spring Security架構(gòu)以及源碼詳析

    這篇文章主要給大家介紹了關(guān)于Spring Security架構(gòu)以及源碼的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2018-06-06
  • Java泛型的使用限制實(shí)例分析

    Java泛型的使用限制實(shí)例分析

    這篇文章主要介紹了Java泛型的使用限制,結(jié)合實(shí)例形式分析了不能使用java泛型的情況以及泛型使用的相關(guān)注意事項(xiàng),需要的朋友可以參考下
    2019-08-08
  • Java實(shí)現(xiàn)異步執(zhí)行的8種方式總結(jié)

    Java實(shí)現(xiàn)異步執(zhí)行的8種方式總結(jié)

    這篇文章主要給大家介紹了關(guān)于Java實(shí)現(xiàn)異步執(zhí)行的8種方式,異步編程不會(huì)阻塞程序的執(zhí)行,它將耗時(shí)的操作提交給后臺(tái)線程或其他執(zhí)行環(huán)境,并立即返回,使得程序可以繼續(xù)執(zhí)行其他任務(wù),需要的朋友可以參考下
    2023-09-09
  • 2020最新IDEA SpringBoot整合Dubbo的實(shí)現(xiàn)(zookeeper版)

    2020最新IDEA SpringBoot整合Dubbo的實(shí)現(xiàn)(zookeeper版)

    這篇文章主要介紹了2020最新IDEA SpringBoot整合Dubbo(zookeeper版),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09
  • Java中Connection timed out和Connection refused的區(qū)別講解

    Java中Connection timed out和Connection refused的區(qū)別講解

    今天小編就為大家分享一篇關(guān)于Java中Connection timed out和Connection refused的區(qū)別講解,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2019-04-04
  • Java實(shí)現(xiàn)文件的加密解密功能示例

    Java實(shí)現(xiàn)文件的加密解密功能示例

    這篇文章主要介紹了Java實(shí)現(xiàn)文件的加密解密功能,結(jié)合具體實(shí)例形式詳細(xì)分析了java針對(duì)文件的讀取、判斷、加密、解密等相關(guān)步驟與操作技巧,需要的朋友可以參考下
    2017-10-10

最新評(píng)論

德江县| 环江| 久治县| 宣城市| 永宁县| 中方县| 九江县| 额敏县| 宁津县| 通化市| 湾仔区| 玛曲县| 海门市| 吉隆县| 齐河县| 阿合奇县| 会东县| 额尔古纳市| 博湖县| 郯城县| 宕昌县| 东光县| 南京市| 巴东县| 普定县| 和田市| 深泽县| 休宁县| 南乐县| 林州市| 富民县| 西宁市| 墨竹工卡县| 襄汾县| 报价| 邯郸市| 囊谦县| 建水县| 基隆市| 台中县| 息烽县|