使用 Qualifiers 微調基於註解的自動裝配
@Primary
和 @Fallback
是在使用型別進行自動裝配時,存在多個候選項且能夠確定一個主選(或非回退)候選 bean 的有效方式。
當你需要對選擇過程進行更多控制時,可以使用 Spring 的 @Qualifier
註解。你可以將限定符值與特定引數關聯,從而縮小型別匹配的範圍,以便為每個引數選擇特定的 bean。在最簡單的情況下,這可以是一個純粹的描述性值,如下例所示
-
Java
-
Kotlin
public class MovieRecommender {
@Autowired
@Qualifier("main")
private MovieCatalog movieCatalog;
// ...
}
class MovieRecommender {
@Autowired
@Qualifier("main")
private lateinit var movieCatalog: MovieCatalog
// ...
}
你也可以在單獨的建構函式引數或方法引數上指定 @Qualifier
註解,如下例所示
-
Java
-
Kotlin
public class MovieRecommender {
private final MovieCatalog movieCatalog;
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(@Qualifier("main") MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
class MovieRecommender {
private lateinit var movieCatalog: MovieCatalog
private lateinit var customerPreferenceDao: CustomerPreferenceDao
@Autowired
fun prepare(@Qualifier("main") movieCatalog: MovieCatalog,
customerPreferenceDao: CustomerPreferenceDao) {
this.movieCatalog = movieCatalog
this.customerPreferenceDao = customerPreferenceDao
}
// ...
}
下例展示了相應的 bean 定義。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog">
<qualifier value="main"/> (1)
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<qualifier value="action"/> (2)
<!-- inject any dependencies required by this bean -->
</bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>
1 | 帶有 main 限定符值的 bean 將與帶有相同限定符值的建構函式引數進行自動裝配。 |
2 | 帶有 action 限定符值的 bean 將與帶有相同限定符值的建構函式引數進行自動裝配。 |
對於回退匹配,bean 名稱被視為預設的限定符值。因此,你可以定義一個 id
為 main
的 bean,而不是使用巢狀的 qualifier 元素,這會產生相同的匹配結果。然而,儘管你可以使用這種約定按名稱引用特定的 bean,但 @Autowired
本質上是關於型別驅動的注入,並帶有可選的語義限定符。這意味著限定符值,即使是 bean 名稱回退,也始終在型別匹配的集合內具有縮小範圍的語義。它們在語義上不表示對唯一 bean id
的引用。好的限定符值如 main
或 EMEA
或 persistent
,它們表達了特定元件的特性,這些特性獨立於 bean id
,而 bean id
在匿名 bean 定義(如上例所示)的情況下可能是自動生成的。
限定符也適用於帶型別的集合,如前所述——例如,對於 Set<MovieCatalog>
。在這種情況下,根據宣告的限定符匹配到的所有 bean 都將作為一個集合注入。這意味著限定符不必是唯一的。相反,它們構成過濾條件。例如,你可以定義多個 MovieCatalog
bean,它們都具有相同的限定符值“action”,所有這些 bean 都將被注入到用 @Qualifier("action")
註解的 Set<MovieCatalog>
中。
讓限定符值在型別匹配的候選項中根據目標 bean 名稱進行選擇,這在注入點不需要 從 6.1 版本開始,這需要存在 |
作為按名稱注入的一種替代方案,可以考慮 JSR-250 的 @Resource
註解,其語義定義是透過其唯一名稱來標識特定的目標元件,而宣告的型別與匹配過程無關。@Autowired
的語義則大不相同:在按型別選擇候選 bean 後,指定的 String
限定符值僅在這些按型別選擇的候選項中考慮(例如,將 account
限定符與標記有相同限定符標籤的 bean 進行匹配)。
對於自身被定義為集合、Map
或陣列型別的 bean,@Resource
是一個不錯的解決方案,它透過唯一名稱引用特定的集合或陣列 bean。話雖如此,你也可以透過 Spring 的 @Autowired
型別匹配演算法來匹配集合、Map
和陣列型別,只要元素型別資訊在 @Bean
返回型別簽名或集合繼承層級中得到保留。在這種情況下,你可以使用限定符值來選擇同類型的集合,如前一段所述。
@Autowired
也考慮自身引用進行注入(即,引用回當前被注入的 bean)。有關詳細資訊,請參閱自身注入。
@Autowired
適用於欄位、建構函式和多引數方法,允許透過在引數級別使用限定符註解來縮小範圍。相比之下,@Resource
僅支援欄位和帶單引數的 bean 屬性 setter 方法。因此,如果你的注入目標是建構函式或多引數方法,你應該堅持使用限定符。
你可以建立自己的自定義限定符註解。為此,定義一個註解並在你的定義中提供 @Qualifier
註解,如下例所示
-
Java
-
Kotlin
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {
String value();
}
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class Genre(val value: String)
然後你可以在自動裝配的欄位和引數上提供自定義限定符,如下例所示
-
Java
-
Kotlin
public class MovieRecommender {
@Autowired
@Genre("Action")
private MovieCatalog actionCatalog;
private MovieCatalog comedyCatalog;
@Autowired
public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
this.comedyCatalog = comedyCatalog;
}
// ...
}
class MovieRecommender {
@Autowired
@Genre("Action")
private lateinit var actionCatalog: MovieCatalog
private lateinit var comedyCatalog: MovieCatalog
@Autowired
fun setComedyCatalog(@Genre("Comedy") comedyCatalog: MovieCatalog) {
this.comedyCatalog = comedyCatalog
}
// ...
}
接下來,你可以為候選 bean 定義提供資訊。你可以在 <bean/>
標籤下新增 <qualifier/>
標籤作為子元素,然後指定 type
和 value
來匹配你的自定義限定符註解。型別與註解的完全限定類名匹配。或者,為了方便,如果不存在名稱衝突的風險,你可以使用短類名。下例展示了這兩種方法
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog">
<qualifier type="Genre" value="Action"/>
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<qualifier type="example.Genre" value="Comedy"/>
<!-- inject any dependencies required by this bean -->
</bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>
在類路徑掃描和託管元件中,你可以看到一種基於註解的替代方案,用於在 XML 中提供限定符元資料。具體來說,請參閱使用註解提供限定符元資料。
在某些情況下,使用不帶值的註解可能就足夠了。當註解具有更通用的用途,並且可以應用於幾種不同型別的依賴項時,這會很有用。例如,你可能提供一個離線目錄,在沒有網際網路連線時可以搜尋它。首先,定義簡單註解,如下例所示
-
Java
-
Kotlin
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {
}
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class Offline
然後將註解新增到需要自動裝配的欄位或屬性上,如下例所示
-
Java
-
Kotlin
public class MovieRecommender {
@Autowired
@Offline (1)
private MovieCatalog offlineCatalog;
// ...
}
1 | 此行添加了 @Offline 註解。 |
class MovieRecommender {
@Autowired
@Offline (1)
private lateinit var offlineCatalog: MovieCatalog
// ...
}
1 | 此行添加了 @Offline 註解。 |
現在 bean 定義只需要一個 qualifier type
,如下例所示
<bean class="example.SimpleMovieCatalog">
<qualifier type="Offline"/> (1)
<!-- inject any dependencies required by this bean -->
</bean>
1 | 此元素指定了限定符。 |
你還可以定義接受命名屬性(除了或代替簡單的 value
屬性)的自定義限定符註解。如果需要在自動裝配的欄位或引數上指定多個屬性值,則 bean 定義必須匹配所有這些屬性值才能被視為自動裝配的候選 bean。例如,考慮以下註解定義
-
Java
-
Kotlin
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {
String genre();
Format format();
}
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class MovieQualifier(val genre: String, val format: Format)
在本例中,Format
是一個列舉型別,定義如下
-
Java
-
Kotlin
public enum Format {
VHS, DVD, BLURAY
}
enum class Format {
VHS, DVD, BLURAY
}
需要自動裝配的欄位使用自定義限定符進行註解,幷包含兩個屬性的值:genre
和 format
,如下例所示
-
Java
-
Kotlin
public class MovieRecommender {
@Autowired
@MovieQualifier(format=Format.VHS, genre="Action")
private MovieCatalog actionVhsCatalog;
@Autowired
@MovieQualifier(format=Format.VHS, genre="Comedy")
private MovieCatalog comedyVhsCatalog;
@Autowired
@MovieQualifier(format=Format.DVD, genre="Action")
private MovieCatalog actionDvdCatalog;
@Autowired
@MovieQualifier(format=Format.BLURAY, genre="Comedy")
private MovieCatalog comedyBluRayCatalog;
// ...
}
class MovieRecommender {
@Autowired
@MovieQualifier(format = Format.VHS, genre = "Action")
private lateinit var actionVhsCatalog: MovieCatalog
@Autowired
@MovieQualifier(format = Format.VHS, genre = "Comedy")
private lateinit var comedyVhsCatalog: MovieCatalog
@Autowired
@MovieQualifier(format = Format.DVD, genre = "Action")
private lateinit var actionDvdCatalog: MovieCatalog
@Autowired
@MovieQualifier(format = Format.BLURAY, genre = "Comedy")
private lateinit var comedyBluRayCatalog: MovieCatalog
// ...
}
最後,bean 定義應該包含匹配的限定符值。此示例還演示了你可以使用 bean 元屬性(meta attributes)代替 <qualifier/>
元素。如果存在 <qualifier/>
元素及其屬性,則它們優先,但如果沒有這樣的限定符,自動裝配機制將回退到 <meta/>
標籤中提供的值,如下例中的最後兩個 bean 定義所示
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog">
<qualifier type="MovieQualifier">
<attribute key="format" value="VHS"/>
<attribute key="genre" value="Action"/>
</qualifier>
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<qualifier type="MovieQualifier">
<attribute key="format" value="VHS"/>
<attribute key="genre" value="Comedy"/>
</qualifier>
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<meta key="format" value="DVD"/>
<meta key="genre" value="Action"/>
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<meta key="format" value="BLURAY"/>
<meta key="genre" value="Comedy"/>
<!-- inject any dependencies required by this bean -->
</bean>
</beans>