透過限定符最佳化基於註解的自動裝配
當可以確定一個主(或非備用)候選時,@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。也就是說,您也可以透過 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 定義提供資訊。您可以新增 <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>