使用限定詞微調基於註解的自動裝配
當可以確定一個主要(或非回退)候選者時,@Primary
和 @Fallback
是使用類型自動裝配多個實例的有效方法。
當您需要更精確地控制選擇過程時,可以使用 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,而不是巢狀限定詞元素,從而產生相同的比對結果。但是,儘管您可以使用此慣例按名稱引用特定 Bean,但 @Autowired
本質上是關於類型驅動的注入,帶有可選的語意限定詞。這表示限定詞值,即使使用 Bean 名稱回退,也始終在類型比對集中具有縮小語意。它們在語意上並不表示對唯一 Bean id
的引用。良好的限定詞值是 main
或 EMEA
或 persistent
,表達特定元件的特性,這些特性獨立於 Bean id
,在匿名 Bean 定義(例如前一個範例中的定義)的情況下,Bean id
可能是自動產生的。
限定詞也適用於類型化集合,如先前討論的範例,例如 Set<MovieCatalog>
。在這種情況下,根據宣告的限定詞,所有符合的 Bean 都會以集合形式注入。這表示限定詞不必是唯一的。相反地,它們構成篩選條件。例如,您可以定義多個具有相同限定詞值「action」的 MovieCatalog
Bean,所有這些 Bean 都會注入到以 @Qualifier("action")
註解的 Set<MovieCatalog>
中。
在類型比對的候選者中,讓限定詞值根據目標 Bean 名稱進行選擇,不需要在注入點使用 自 6.1 版起,這需要存在 |
作為依名稱注入的替代方案,請考慮 JSR-250 @Resource
註解,其語意定義為依其唯一名稱識別特定目標元件,而宣告的類型與比對過程無關。@Autowired
具有相當不同的語意:在依類型選擇候選 Bean 之後,指定的 String
限定詞值僅在這些類型選擇的候選者中考慮(例如,將 account
限定詞與標記有相同限定詞標籤的 Bean 進行比對)。
對於本身定義為集合、Map
或陣列類型的 Bean,@Resource
是一個很好的解決方案,依唯一名稱引用特定集合或陣列 Bean。也就是說,只要元素類型資訊保留在 @Bean
傳回類型簽章或集合繼承階層中,您也可以透過 Spring 的 @Autowired
類型比對演算法比對集合、Map
和陣列類型。在這種情況下,您可以使用限定詞值在相同類型的集合之間進行選擇,如前一段所述。
@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 定義提供資訊。您可以新增 <qualifier/>
標籤作為 <bean/>
標籤的子元素,然後指定 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 定義只需要限定詞 type
,如下例所示
<bean class="example.SimpleMovieCatalog">
<qualifier type="Offline"/> (1)
<!-- inject any dependencies required by this bean -->
</bean>
1 | 此元素指定限定詞。 |
您也可以定義自訂限定詞註解,除了簡單的 value
屬性之外,或取代它,還可以接受具名屬性。如果隨後在要自動裝配的欄位或參數上指定了多個屬性值,則 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 中繼屬性來取代 <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>