- 在簡單了解IoC與DI中我們已經了解了DI的基本操作,接下來我們來詳解DI。(IoC詳解請看這里)
- 我們已經知道DI是“你給我,我不用自己創建”的原則。現在我們來看看Spring是如何實現“給”這個動作的,也就是依賴注入的幾種方式。
Spring主要提供了三種注入方式,都是通過 @Autowired
注解配合完成的:
- 屬性注入 (Field Injection):直接在字段上使用
@Autowired
。 - 構造方法注入 (Constructor Injection):在類的構造方法上使用
@Autowired
。 - Setter 注入 (Setter Injection):在Setter方法上使用
@Autowired
。
三種注入方式
屬性注入
方式:直接在需要注入的屬性(字段)上加上 @Autowired
。
代碼示例:
首先,我們有一個 UserService
Bean:
@Service // 告訴Spring:我是UserService,請你管理我
public class UserService {public void sayHi() {System.out.println("Hi, UserService");}
}
然后,在 UserController
中注入 UserService
:
@Controller
public class UserController {@Autowired // 告訴Spring:我需要一個UserService,請你直接注入到這個屬性里private UserService userService;public void sayHi() {System.out.println("Hi, UserController...");userService.sayHi(); // 使用注入的UserService}
}
獲取并使用:
@SpringBootApplication
public class SpringTocDemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringTocDemoApplication.class, args);UserController userController = context.getBean(UserController.class);userController.sayHi();}
}
運行結果:
Hi, UserController...
Hi, UserService
解釋:Spring成功地將 UserService
實例注入到了 UserController
的 userService
屬性中。
注意:如果你嘗試在沒有Spring容器的情況下直接調用 UserController
的 sayHi()
方法,userService
會是 null
,導致空指針異常(NPE),因為Spring沒有機會注入它。
構造方法注入
方式:在類的構造方法上加上 @Autowired
。Spring會在創建這個類實例時,通過調用這個構造方法來注入依賴。
代碼示例:
@Controller
public class UserController2 {private UserService userService;@Autowired // 告訴Spring:請通過這個構造方法注入UserServicepublic UserController2(UserService userService) {this.userService = userService;}public void sayHi() {System.out.println("Hi, UserController2...");userService.sayHi();}
}
注意:
- 如果一個類只有一個構造方法,那么即使不加
@Autowired
,Spring也會自動使用它進行注入(Spring 4.3+)。
[!TIP] 此時默認的無參構造方法不生效了,最好的習慣是補上默認的無參構造方法
Spring
默認的是使用無參的構造方法(如有多個構造方法)
- 這樣會導致之后在使用到
userService
的地方有空指針報錯異常(可以通過打印日志的方法來確定具體使用哪一個方法)
- 所以有多個構造方法的情況下,需要在需要的構造方法上加上
@Autowired
注解表示指定使用哪一個
Setter 注入
方式:在Setter方法上加上 @Autowired
。Spring會先創建對象,然后調用對應的Setter方法來注入依賴。
代碼示例:
@Controller
public class UserController3 {private UserService userService;@Autowired // 告訴Spring:請通過這個Setter方法注入UserServicepublic void setUserService(UserService userService) {this.userService = userService;}public void sayHi() {System.out.println("Hi, UserController3...");userService.sayHi();}
}
[!QUESTION] 如果
setUserService
方法上沒有@Autowired
,Spring還能注入嗎?
不能。Spring需要@Autowired
來知道這個方法是用來注入依賴的。
三種注入方式優缺點分析
#面經
方式 | 優點 | 缺點 |
---|---|---|
屬性注入 | 最簡潔,使用方便。 | 1. 違反單一職責原則(SRP)。 2. 難以測試(只能用于IoC容器,并且在使用的時候才會出現空指針異常)。 3. 無法聲明為 final 。 |
構造方法注入 | 1. 依賴明確,強制依賴。 2. 可以聲明為 final (推薦)。3. 保證對象完全初始化。因為依賴是在類的構造方法中執行的,而構造方法是在類加載階段就會執行的方法. 4. 更好的可測試性。 5. 通用性好,因為構造方法是JDK支持的,所以換任何框架都是使用的。 | 1. 依賴過多時,構造方法參數列表會很長。 |
Setter 注入 | 1. 依賴可選(不強制)。 2. 可以在對象創建后進行配置。 | 1. 無法聲明為 final 。2. 可能會被多次調用,存在風險。 |
推薦:在大多數情況下,構造方法注入是最佳實踐。它確保了依賴的不可變性,并使對象在創建時就處于完全可用的狀態。
@Autowired
存在問題?
問題:如果Spring容器中有多個相同類型的Bean,@Autowired
會怎么做?
代碼示例:
我們定義了兩個 User
類型的Bean,名字分別是 user1
和 user2
:
@Configuration
public class BeanConfig {@Bean("user1")public User user1() { /* ... */ }@Bean("user2")public User user2() { /* ... */ }
}
現在,我們在 UserController
中嘗試注入 User
:
@Controller
public class UserController {@Autowiredprivate UserService userService; // 假設只有一個UserService@Autowiredprivate User user; // 嘗試注入Userpublic void sayHi() {System.out.println("Hi, UserController...");userService.sayHi();System.out.println(user); // 打印注入的User}
}
運行結果:
// 報錯:NoUniqueBeanDefinitionException: No qualifying bean of type 'User' available: expected single matching bean but found 2
解釋:Spring發現有兩個 User
類型的Bean(user1
和 user2
),它不知道該注入哪一個,所以報錯了。
如何解決“多個相同類型Bean”的問題?
Spring提供了幾種方式來解決歧義:
@Primary
:優先注入。@Qualifier
:指定Bean的名稱。@Qualifier
優先級比@Primary
高
@Resource
:按名稱注入(JDK標準)。
@Primary
作用:當有多個相同類型的Bean時,給其中一個加上 @Primary
,表示它是首選的。
代碼示例:
@Configuration
public class BeanConfig {@Bean("user1")@Primary // 告訴Spring:當需要User類型時,優先注入我public User user1() { /* ... */ }@Bean("user2")public User user2() { /* ... */ }
}
現在,UserController
再次注入 User
:
@Controller
public class UserController {@Autowiredprivate User user; // 會自動注入user1// ...
}
解釋:當Spring需要注入 User
類型時,它會優先選擇帶有 @Primary
的 user1
。
@Qualifier
作用:明確指定要注入哪個Bean的名稱。
代碼示例:
@Controller
public class UserController {@Autowired@Qualifier("user2") // 告訴Spring:我要注入名為“user2”的User Beanprivate User user;// ...
}
解釋:@Qualifier
必須和 @Autowired
一起使用。它告訴Spring,即使有多個 User
類型的Bean,我只想要那個名字是 user2
的。
[!IMPORTANT] @Qualifier注解不能單獨使?,必須配合@Autowired使?
@Resource
作用:@Resource
是JDK提供的注解,它默認是按名稱進行注入。如果找不到同名的Bean,再嘗試按類型注入。
代碼示例:
@Controller
public class UserController {@Resource(name = "user1") // 告訴Spring:我要注入名為“user1”的Beanprivate User user;// ...
}
[[四種不同情況反應構造Spring框架中Bean的依賴注入和自動裝配的不同規則]]
#面經
@Autowired
vs @Resource
的區別:
@Autowired
是spring提供的注解,@Resource
是JDK提供的注解
@Autowired
:Spring特有的,默認按類型注入。如果類型有多個,再嘗試按名稱匹配。@Resource
:JDK標準,默認按名稱注入。如果名稱找不到,再嘗試按類型匹配,并且其支持更多的參數設置,如name
:根據名稱獲取Bean