上篇文章Spring IOC的核心機制:例項化與注入我們提到在有多個實現類的情況下,spring是如何選擇特定的bean將其注入到程式碼片段中,我們討論了按照名稱注入和使用@Qualifier 註解輸入的兩種方式,本篇文章將結合之前提到的和spring的其他注入方式一起進行討論。
本文主題
我們將討論在一個介面或者抽象類在具有多個實現類的情況下,有多少種策略能夠讓我們在特定的程式碼片段中注入想要的bean。
按照名稱
定義一個Skill藉口,他描述了英雄具備哪些技能,現在有兩個英雄的實現類Diana和Irelia,通過component註解將其標註為bean,在容器啟動的時候會將他們載入到容器中。
現在有一個BannerController要使用Diana這個bean
@RestController
public class BannerController {
@Autowired
private Skill diana;
@RequestMapping(value = "/v2/banner", method = {RequestMethod.GET})
public String test() {
diana.r();
return "Hello SpringBoot";
}
}
private Skill diana;
這裡將成員變數的名字寫成Diana這個bean的名字,容器就會選擇Diana這個注入,這就叫做按照名稱注入。spring會給每個bean設定預設到的名字也可以自定義,大家自行查詢資料瞭解即可。
@Qualifier 註解
還是上文的例子,如果成員變數名不寫成bean的名稱,是其他的名字,比如按照常規的寫法會寫成
private Skill skill;
這個時候就可以使用@Qualifier 註解,
@RestController
public class BannerController {
@Autowired
@Qualifier("diana")
private Skill skill;
@RequestMapping(value = "/v2/banner", method = {RequestMethod.GET})
public String test() {
skill.r();
return "Hello SpringBoot";
}
有選擇的注入一個bean,註釋掉某個bean上的@Component
如果我們確定要使用哪個bean,那可以把其他的註釋掉
//@Component
public class Irelia implements Skill {
public Irelia() {
System.out.println("Hello, Irelia");
}
public void q(){
System.out.println("Irelia Q");
}
public void w(){
System.out.println("Irelia W");
}
public void e(){
System.out.println("Irelia E");
}
public void r(){
System.out.println("Irelia R");
}
}
讓Skill的實現類只有一個,自然就不要再操心注入哪個了,就只會注入存在於容器當中的惟一的那一個了。
使用@Primary提高bean的優先順序
還可以使用@Primary註解提高我們想讓注入bean的優先順序,那spring在掃描到相同型別的bean的時候,就會看誰的優先順序高,誰高誰注入
@Component
@Primary
public class Diana implements Skill {
private String skillName = "Diana R";
public Diana() {
System.out.println("I am Diana");
}
public void q(){
System.out.println("Diana Q");
}
public void w(){
System.out.println("Diana W");
}
public void e(){
System.out.println("Diana E");
}
public void r(){
System.out.println(this.skillName);
}
}
然後啟動啟動程式,訪問路由,就會看到下面的輸出:
說明確實是注入了Diana這個bean。
階段總結
上面的實現方式看起來都能實現按照所需注入特定的bean,但是有個問題,當發生變化的時候,這裡的變化可能是英雄名變了,新增英雄或者要替換其他英雄,都需要去手動改程式碼,有些情況下可能還不只改一處,還是比較繁瑣,並不方便。那有沒有一種方式可以通過配置檔案的形式來指定要注入的bean,當變化發生,只需要改配置,那豈不是很方便很靈活很開心。
條件註解
條件註解顧名思義,就是按照條件決定注入哪一個bean,所以要想使用條件註解,就得先寫一個條件。
spring為我們提供了特定的方式來實現自己的條件:@Conditional註解+Condition介面,spring的這種方式的原理是把符合條件的bean加載入到容器中,不合的不載入,那這樣在注入的時候就不會有多個相同型別的bean存在了,自然也就注入成功了。
定義一個類實現Condition介面
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class HeroCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return false;
}
}
實現Condition介面必須實現它的matches方法,我們先來解釋一下這個方法的入參和返回值,
ConditionContext是一個介面,定義了他支援那些操作
public interface ConditionContext {
BeanDefinitionRegistry getRegistry();
ConfigurableListableBeanFactory getBeanFactory();
Environment getEnvironment();
ResourceLoader getResourceLoader();
ClassLoader getClassLoader();
}
其中的getEnvironment方法會返回Environment物件,他裡面會記錄當前應用所有的環境資訊,可以通過這個物件獲取到我們的配置資訊。
返回值是Boolean型別的,哪個bean的條件匹配成功,就會把這個bean注入到程式碼片段中去。我們來具體實現一下。
public class HeroCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String name = context.getEnvironment().getProperty("hero.condition");
System.out.println(name);
if ("diana".equalsIgnoreCase(name)){
return true;
}else if ("irelia".equalsIgnoreCase(name)){
return true;
}
return false;
}
}
@Configuration
public class HeroConfiguration {
@Bean
@Conditional(HeroCondition.class)
public Skill diana(){
return new Diana();
}
@Bean
@Conditional(HeroCondition.class)
public Skill irelia(){
return new Irelia();
}
}
通過以上的方式,就可以完成一個簡單的條件註解,但是這種需求其實spring boot早已經幫我們實現了,提供了一些註解可以使用,下篇文章我們看看使用spring boot的內建註解,如何來解決我們的問題。
部落格地址:https://www.immortalp.com/
歡迎大家去 我的部落格 瞅瞅,裡面有更多關於測試實戰的內容哦!!