無論選擇是什么,Spring 都能容納這兩種風格,甚至可以將它們混合在一起。值得指出的是,通過它的 JAVA 配置選項,Spring 允許注解以一種非入侵的方式使用,不觸碰目標組件源碼和那些工具,所有的配置風格由 Spring 工具套件支持。
基于注解的配置提供了一種 XML 設置的可替代方式,它依賴于字節碼元數據來連接組件,而不是用尖括號聲明的方式。代替使用 XML 來描述 bean 連接,開發者通過將注解使用在相關的類,方法或字段聲明中,將配置移動到了組件類本身的內部。正如在“Example: The
RequiredAnnotationBeanPostProcessor”那節提到的那樣,使用BeanPostProcessor與注解結合是擴展 Spring IoC 容器的的常見方法。例如,Spring 2.0 引入了@Required注解來執行需要的屬性的可能性。Spring 2.5 使以同樣地通用方法來驅動 Spring 的依賴注入變為可能。本質上來說,@Autowired提供了如 3.4.5 小節描述的同樣的能力。“Autowiring collaborators”但更細粒度的控制和更廣的應用性。Spring 2.5 也添加對 JSR-250 注解的支持,例如,@PostConstruct和@PreDestroy
。Spring 3.0 添加了對 JSR-330,包含在javax.inject包內的注解(Java 的依賴注入)的支持,例如@Inject和@Named。關于這些注解的細節可以在相關的小節找到。
注解注入在 XML 注入之前進行,因此對于通過兩種方法進行組裝的屬性后者的配置會覆蓋前者。
跟以前一樣,你可以作為單獨的 bean 定義來注冊它們,但也可以通過在一個基于 XML 的 Spring 配置(注入包含上下文命名空間)中包含下面的標簽來隱式的注冊它們:
<?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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
context:annotation-config/
</beans>
(隱式注冊的后處理器包括
AutowiredAnnotationBeanPostProcessor,
CommonAnnotationBeanPostProcessor,
PersistenceAnnotationBeanPostProcessor和前面提到的
RequiredAnnotationBeanPostProcessor。)
<
context:annotation-config/>僅在定義它的同樣的應用上下文中尋找注解的 beans。這意味著,如果你在一個為DispatcherServlet服務的WebApplicationContext中放置了<context:annotation-config/>,它只能在你的控制器中尋找@Autowired注解的 beans,而不是在你的服務層中。更多信息請看 18.2 小節,“The DispatcherServlet”。
3.9.1 @Required
@Required注解應用到 bean 屬性的 setter 方法上,例子如下:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Required
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
這個注解僅僅是表明受影響的 bean 屬性必須在配置時通過顯式的 bean 定義或自動組裝填充。如果受影響的 bean 屬性沒有填充,容器會拋出一個異常,這允許及早明確的失敗,避免NullPointerExceptions或后面出現類似的情況。仍然建議你在 bean 類本身加入斷言,例如,加入到初始化方法中。這樣做可以強制這些需要的引用和值,甚至是你在容器外部使用這個類的時候。
2 @Autowired
===========================================================================
在下面的例子中 JSR 330 的@Inject注解可以用來代替 Spring 的@Autowired注解。
你可以將@Autowired注解應用到構造函數上。
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
從 Spring 框架 4.3 起,如果目標 bena 僅定義了一個構造函數,那么@Autowired注解的構造函數不再是必要的。如果一些構造函數是可獲得的,至少有一個必須要加上注解,以便于告訴容器使用哪一個。
正如預料的那樣,你也可以將@Autowired注解應用到“傳統的”setter 方法上:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
你也可以應用注解到具有任何名字和/或多個參數的方法上:
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
你也可以應用@Autowired到字段上,甚至可以與構造函數混合用:
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
private MovieCatalog movieCatalog;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
通過給帶有數組的字段或方法添加@Autowired注解,也可以從ApplicationContext中提供一組特定類型的 bean:
public class MovieRecommender {
@Autowired
private MovieCatalog[] movieCatalogs;
// ...
}
同樣也可以應用到具有同一類型的集合上:
public class MovieRecommender {
private Set<MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
如果你希望數組或列表中的項按指定順序排序,你的 bean 可以實現
org.springframework.core.Ordered接口,或使用@Order或標準@Priority注解。
只要期望的 key 是String,那么類型化的 Maps 就可以自動組裝。Map 的值將包含所有期望類型的 beans,key 將包含對應的 bean 名字:
public class MovieRecommender {
private Map<String, MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
默認情況下,當沒有候選 beans 可獲得時,自動組裝會失敗;默認的行為是將注解的方法,構造函數和字段看作指明了需要的依賴。這個行為也可以通過下面的方式去改變。
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired(required=false)
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
每個類只有一個構造函數可以標記為必需的,但可以注解多個非必需的構造函數。在這種情況下,會考慮這些候選者中的每一個,Spring 使用最貪婪的構造函數,即依賴最滿足的構造函數,具有最大數目的參數。
建議在@Required注解之上使用@Autowired的required特性。required特性表明這個屬性自動裝配是不需要的,如果這個屬性不能被自動裝配,它會被忽略。另一方面@Required是更強大的,在它強制這個屬性被任何容器支持的 bean 設置。如果沒有值注入,會拋出對應的異常。
你也可以對那些已知的具有可解析依賴的接口使用@Autowired:BeanFactory,ApplicationContext,Environment, ResourceLoader,ApplicationEventPublisher和MessageSource。這些接口和它們的擴展接口,例如
ConfigurableApplicationContext或ResourcePatternResolver,可以自動解析,不需要特別的設置。
public class MovieRecommender {
@Autowired
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
@Autowired、@Inject、@Resource和@Value都是通過 Spring 的BeanPostProcessor實現處理的,這反過來意味著,你不能在自己的BeanPostProcessor或BeanFactoryPostProcessor中使用這些注解。
這些類型必須顯式通過 XML 或使用 Spring 的@Bean方法來裝配。
3 用 @Primary 微調基于注解的自動裝配
因為根據類型的自動裝配可能會導致多個候選目標,所以在選擇過程中進行更多的控制經常是有必要的。一種方式通過 Spring 的@Primary注解來完成。當有個多個候選 bean 要組裝到一個單值的依賴時,@Primary表明指定的 bean 應該具有更高的優先級。如果確定一個’primary’ bean 位于候選目標中間,它將是那個自動裝配的值。
假設我們具有如下配置,將firstMovieCatalog定義為主要的MovieCatalog。
@Configuration
public class MovieConfiguration {
@Bean
@Primary
public MovieCatalog firstMovieCatalog() { ... }
@Bean
public MovieCatalog secondMovieCatalog() { ... }
// ...
}
根據這樣的配置,下面的MovieRecommender將用firstMovieCatalog進行自動裝配。
public class MovieRecommender {
@Autowired
private MovieCatalog movieCatalog;
// ...
}
對應的 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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
context:annotation-config/
<bean class="example.SimpleMovieCatalog" primary="true">
</bean>
<bean class="example.SimpleMovieCatalog">
</bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>
3.9.4 微調基于注解且帶有限定符的自動裝配
當有多個實例需要確定一個主要的候選對象時,@Primary是一種按類型自動裝配的有效方式。當需要在選擇過程中進行更多的控制時,可以使用 Spring 的@Qualifier注解。為了給每個選擇一個特定的 bean,你可以將限定符的值與特定的參數聯系在一起,減少類型匹配集合。在最簡單的情況下,這是一個純描述性值:
public class MovieRecommender {
@Autowired
@Qualifier("main")
private MovieCatalog movieCatalog;
// ...
}
@Qualifier注解也可以指定單個構造函數參數或方法參數:
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(@Qualifier("main")MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
對應的 bean 定義如下。限定符值為”main”的 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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
context:annotation-config/
<bean class="example.SimpleMovieCatalog">
<qualifier value="main"/>
</bean
<bean class="example.SimpleMovieCatalog">
<qualifier value="action"/>
</bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>
對于回退匹配,bean 名字被認為是默認的限定符值。因此你可以定義一個 id 為main的 bean 來代替內嵌的限定符元素,會有同樣的匹配結果。然而,盡管你可以使用這個約定根據名字引用特定的 beans,但是@Autowired從根本上來講是使用可選的語義限定符來進行類型驅動注入的。這意味著限定符的值,即使回退到 bean 名稱,總是縮小語義類型匹配的集合;它們沒有從語義上將一個引用表達為一個唯一的 bean id。好的限定符值是”main”或”EMEA”或”persistent”,表達一個特定組件的性質,這個組件是獨立于 bean id的,即使前面例子中像這個 bean 一樣的匿名 bean 會自動生成 id。
正如前面討論的那樣,限定符也可以應用到類型結合上,例如,Set<MovieCatalog>。在這個例子中,根據聲明的限定符匹配的所有 beans 作為一個集合進行注入。這意味著限定符不必是唯一的;它們只是構成過濾標準。例如,你可以定義多個具有同樣限定符值”action”的MovieCatalog,所有的這些都將注入到帶有注解@Qualifier("action")的Set<MovieCatalog>中。
如果你想通過名字表達注解驅動的注入,不要主要使用@Autowired,雖然在技術上能通過@Qualifier值引用一個 bean 名字。作為可替代產品,可以使用 JSR-250 @Resource注解,它在語義上被定義為通過組件唯一的名字來識別特定的目標組件,聲明的類型與匹配過程無關。@Autowired有不同的語義:通過類型選擇候選 beans,特定的String限定符值被認為只在類型選擇的候選目標中,例如,在那些標記為具有相同限定符標簽的 beans 中匹配一個”account”限定符。
對于那些本身定義在集合/映射或數組類型中的 beans 來說,@Resource是一個很好的解決方案,適用于特定的集合或通過唯一名字區分的數組 bean。也就是說,自 Spring 4.3 起,集合/映射和數組類型中也可以通過 Spring 的@Autowired類型匹配算法進行匹配,只要元素類型信息在@Bean中保留,返回類型簽名或集合繼承體系。在這種情況下,限定符值可以用來在相同類型的集合中選擇,正如在前一段中概括的那樣。
自 Spring 4.3 起,@Autowired也考慮自引用注入,例如,引用返回當前注入的 bean。注意自注入是備用;普通對其它組件的依賴關系總是優先的。在這個意義上,自引用不參與普通的候選目標選擇,因此尤其是從不是主要的;恰恰相反,它們最終總是最低的優先級。在實踐中,自引用只是作為最后的手段,例如,通過 bean 的事務代理調用同一實例的其它方法:在考慮抽出受影響的方法來分隔代理 bean 的場景中。或者,使用@Resource通過它的唯一名字可能得到一個返回當前 bean 的代理。
@Autowired可以應用到字段,構造函數和多參數方法上,允許通過限定符注解在參數層面上縮減候選目標。相比之下,@Resource僅支持字段和 bean 屬性的帶有單個參數的 setter 方法。因此,如果你的注入目標是一個構造函數或一個多參數的方法,堅持使用限定符。
你可以創建自己的定制限定符注解。簡單定義一個注解,在你自己的定義中提供@Qualifier注解:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {
String value();
}
然后你可以在自動裝配的字段和參數上提供定制的限定符:
public class MovieRecommender {
@Autowired
@Genre("Action")
private MovieCatalog actionCatalog;
private MovieCatalog comedyCatalog;
@Autowired
public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
this.comedyCatalog = comedyCatalog;
}
// ...
}
接下來,提供候選 bean 定義的信息。你可以添加<qualifier/>標記作為<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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
context:annotation-config/
<bean class="example.SimpleMovieCatalog">
<qualifier type="Genre" value="Action"/>
</bean>
<bean class="example.SimpleMovieCatalog">
<qualifier type="example.Genre" value="Comedy"/>
</bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>
在 3.10 小節,“類路徑掃描和管理組件”中,你將看到一個基于注解的替代方法,在 XML 中提供限定符元數據。特別地,看 3.10.8 小節,“用注解提供限定符元數據”。
在某些情況下,使用沒有值的注解就是足夠的。當注解為了通用的目的時,這是非常有用的,可以應用到跨幾個不同類型的依賴上。例如,當網絡不可用時,你可以提供一個要搜索的離線目錄。首先定義一個簡單的注解:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {
}
然后將注解添加到要自動裝配的字段或屬性上:
public class MovieRecommender {
@Autowired
@Offline
private MovieCatalog offlineCatalog;
// ...
}
現在 bean 定義只需要一個限定符類型:
<bean class="example.SimpleMovieCatalog">
<qualifier type="Offline"/>
</bean>
你也可以定義接收命名屬性之外的定制限定符注解或代替簡單的值屬性。如果要注入的字段或參數指定了多個屬性值,bean 定義必須匹配所有的屬性值才會被認為是一個可自動裝配的候選目標。作為一個例子,考慮下面的注解定義:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {
String genre();
Format format();
}
這種情況下Format是枚舉類型:
public enum Format {
VHS, DVD, BLURAY
}
要自動裝配的字段使用定制限定符進行注解,并且包含了兩個屬性值:genre和format。
public class MovieRecommender {
@Autowired