1.9 基於註解的容器配置
Spring配置註解比XML配置更好?
基於註解的配置介紹拋出一個問題,是否比XML方式更好。簡單的回答是看場景。具體的描述是每種方式各有利弊,通常的,這個由開發者去決定更適合他們的策略。由於這種定義的方式,註解在聲明中提供了大量的上下文,導致更短和更簡潔的配置。XML擅長於連接組件,而不需要修改它們的源代碼或重新編譯它們。一些開發人員傾向於閉源,而另一些人則認為被註釋的類不再是pojo,而且配置變得分散且更難控制。
無論怎麼選擇,Spring能夠兼容兩種風格甚至是混合使用。值得指出的是通過JavaConfig,Spring允許以無侵入式方式使用,不需要接觸目標組件源代碼。在工具方面,所有的配置風格通過Spring的Tools Eclipse工具支持。
基於註解的配置提供了XML設置的替代方法,它依靠字節碼元數據來連接組件,而不是尖括號聲明(不需要xml的格式配置)。替換使用XML去描述bean,開發者只需移動配置到類本身並通過在關聯的類、方法、字段上使用註解。在RequiredAnnotationBeanPostProcessor
中提到,結合使用BeanPostProcessor
和註釋是擴展Spring IoC容器的常用方法。例如,Spring 2.0引入了使用@Required
註解 強制執行必需屬性的可能性。Spring 2.5使遵循相同的通用方法來驅動Spring的依賴注入成為可能。實際上,@Autowired
註解提供了相同的能力,在 Autowiring Collaborators中被描述,但是提供了更細粒度和更多的能力。Spring2.5增加對JSR250支持 ,例如:@PostConstruct
、@PreDestroy
。Spring3.0增加對JSR-330 (Java的依賴注入)註解包含在包javax.inject
,例如@Inject
和@Named
。更多關於註解詳情能在相關的文章中找到。
註解注入在XML注入之前被執行。因此,XML配置覆蓋註解對屬性的兩種連接方式。
你可以註冊他們作為獨立的bean定義,但是他們也可以通過基於XML包含下面的標籤被隱的註冊(注意包含context命名空間)。
<?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/>
</beans>
隱式的註冊後置處理器包括:
AutowiredAnnotationBeanPostProcessor
、CommonAnnotationBeanPostProcessor
, PersistenceAnnotationBeanPostProcessor
和前面提到的RequiredAnnotationBeanPostProcessor
<context:annotation-config />
僅在定義它的相同應用程序上下文中查找關於bean的註解。意思是,如果你在WebApplicationContext
中為DispatcherServlet
配置< context:annotation-config/>
,它僅僅檢查@Autowired
在你的Controller層並且不會在你的Service層。查看DispatcherServlet 更多信息。
1.9.1 @Required
@Required
註解應用到bean屬性的Setter方法,類似下面例子:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Required
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
這個註解表示bean屬性在運行時必須被填充賦值,在一個bean定義中顯示賦值或自動裝配。如果受影響的bean屬性沒有被填充值容器拋出一個異常。這允許更早的顯示異常,避免以後再出現NullPointerException
實例等。我們仍然推薦你把斷言放入到bean類本身中(例如,放入初始化方法)。這樣做會強制執行那些必需的引用和值,即使你在容器外部使用該類也是如此。
@Required
註解在Spring5.1中被正式的不推薦使用,更好的方式是,使用構造函數注入要求的設置(或InitializingBean.afterPropertiesSet()
的自定義實現以及bean屬性設置器方法)。
1.9.2 @Autowired
JSR 330的
@Inject
註解能夠使用在Spring的@Autowired
註解的地方。
可以在構造方法上使用@Autowired
,類似下面例子:
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
從Spring4.3開始,如果目標bean僅僅定義以一個構造函數開始,那麼構造函數上的
@Autowired
註解是不必要的。然而,如果有一些構造函數是有效的並且沒有主要默認的構造函數,則至少有一個構造函數必須被註解為@Autowired
,目的是引導容器選擇其中一個去使用。查看構造方法解析詳情。
你也可以應用@Autowired註解到傳統的Setter方法,類似下面例子:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
你可以應用@Autowired
註解到任意方法名和多個參數,類似下面例子:
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;
}
// ...
}
確保你的目標組件(例如,
MovieCatalog
或CustomerPreferenceDao
)是一致性的通過類型聲明,也就是使用基於@Autowired
注入點。否則,注入可能由於在運行時出現“找不到類型匹配”錯誤而失敗。對於基於XML定義的bean或者組件類通過類路徑掃描,容器通常知道具體的類型。然而,對於
@Bean
工廠方法,你需要確保聲明的返回類型足夠的表達。對於實現多個接口的組件,或者對於實現類型可能引用的組件,請考慮在工廠方法中聲明最具體的返回類型。至少與指向bean的注入點所要求的一樣具體(備註:儘可能返回具體的實現類型)。
你還可以引導Spring從ApplicationContext
中提供特定類型的所有bean,通過將@Autowired
註解添加到需要該類型數組的字段或方法中,類似下面例子:
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
註解。否則,它們的順序遵循在容器中對應目標bean定義註冊的順序。你可以在目標類級別和在
@Bean
方法上申明@Order
註解,可能是針對單個bean定(多個定義情況下使用相同的bean類)。@Order
值可能會影響注入點的優先級,不會影響單例bean的啟動順序,這是由依賴關係和@DependsOn
聲明確定。注意:標準的
javax.annotation.Priority
註解在@Bean
級別是無效的,因為它不能被聲明在方法上。它的語義可以通過@Order
值與@Primary
結合在每種類型的單個bean上。
甚至類型Map實例也能被自動裝配只要期望的key類型是String。map的值包含了所有期望類型的bean,並且這些key包含對應bean的名稱,像下面的例子展示:
public class MovieRecommender {
private Map<String, MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
默認情況下,當沒有匹配的候選bean對應給定的注入點的時候自動裝配將失敗。在這種情況下申明數組、集合或map至少有一個期望匹配的元素。
默認行為是將帶註解的方法和字段視為指示所需的依賴項。你可以改變這種行為,像下面這個例子,通過將框架標記為不需要,從而使框架可以跳過不滿意的注入點(通過在@Autowired
屬性中的required
設置為false)。說明:滿足注意條件就注入,不滿足條件就跳過注入。
public class SimpleMovieLister {
private MovieFinder movieFinder;
//自動注入不滿足就不注入
@Autowired(required = false)
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
如果一個非必需的方法的依賴項(或者它的一個依賴項,在有多個參數的情況下)不可用,那麼這個方法將不會被調用。在這種情況下,完全不需要填充非必需字段,將其默認值保留在適當的位置。注入構造函數和工廠方法參數是一種特殊情況,因為由於Spring的構造函數解析算法可能要處理多個構造函數,@Autowired
中的required
屬性有一些不同的含義。
注入的構造函數和工廠方法參數是一種特殊情況,因為由於Spring的構造函數解析算法可能會處理多個構造函數,因此@Autowired
中必填屬性的含義有所不同。默認情況下,有效地需要構造函數和工廠方法參數,但是在單構造函數場景中有一些特殊規則,例如,如果沒有匹配的Bean可用,則將多元素注入點(數組,集合,映射)解析為空實例。這允許一種通用的實現模式,其中所有依賴項都可以在唯一的多參數構造函數中聲明-例如,申明一個簡單的公共構造函數不需要@Autowired
註解。
任何給定的bean類只有一個構造函數可以聲明
@Autowired
並設置屬性required
為true,指示當使用Spring Bean時構造函數自動裝配。因此,如果必填屬性保留為默認值true,則只能使用@Autowired
註解單個構造函數。如果有多個構造函數聲明註解,那麼它們都必須聲明required=false
,以便被認為是自動裝配的候選對象(類似於XML中的autowire=constructor
)。將選擇通過匹配Spring容器中的bean可以滿足的依賴關係數量最多的構造函數如果沒有一個候選者滿意,則將使用主要的/默認構造函數(如果存在)。同樣,如果一個類聲明瞭多個構造函數,但都沒有使用@Autowired
進行註解,則將使用主要的/默認構造函數(如果存在)。如果一個類僅聲明一個單一的構造函數開始,即使沒有註釋,也將始終使用它。請注意,帶註解的構造函數不必是公共的。
在setter方法上,建議使用@Autowired
的required
屬性,而建議使用@Required
註解。設置required
屬性為false則表明這個屬性不是必須的對於自動裝配意圖,並且這個屬性如果不能被自動裝配將被忽略。換句話說,@Required
更強大,因為它強制通過容器支持的任何方式設置屬性,如果沒有定義值,則會引發相應的異常。
另外,你可以通過Java 8的java.util.Optional
來表達特定依賴項的非必需性質,類似以下示例所示:
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(Optional<MovieFinder> movieFinder) {
...
}
}
從Spring5.0後,你可以使用@Nullable
註解(任何包裝中的任何種類-例如,JSR-305的javax.annotation.Nullable
)或者只是利用Kotlin
內置的null
安全支持:
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(@Nullable MovieFinder movieFinder) {
...
}
}
你可以為這些接口使用@Autowired
,這是眾所周知的可注入的依賴:BeanFactory
, ApplicationContext
, Environment
, ResourceLoader
,ApplicationEventPublisher
, 和 MessageSource
。這些接口和它們的拓展接口,例如,ConfigurableApplicationContext
或ResourcePatternResolver
是自動地解析,沒有特殊的安裝需要。下面的例子主動注入一個ApplicationContext
對象:
public class MovieRecommender {
@Autowired
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
@Autowired
、@Inject
、@Value
和@Resource
註解是通過Spring的BeanPostProcessor
實現處理的。這意味著你不能應用這些註解在你自己的BeanPostProcessor
和BeanFactoryPostProcessor
類中。這些類型必須被通過XML或者Spring@Bean
方法連接。參考代碼:
com.liyong.ioccontainer.starter.XmlIocAnnotationContainerConfigrationntainer
1.9.3 使用@Primary
對基於註解的自動裝配調整
因為通過類型的自動裝配可能導致多個候選者,通常有必要需要更多的選擇處理。一種可以完成的方式是使用Spring的@Primary
註解。@Primary
表示當多個bean自動裝配到單值依賴時這個bean是我們更期望的值。如果主要的bean在限定符候選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
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-->
<bean class="example.SimpleMovieCatalog" primary="true">
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<!-- inject any dependencies required by this bean -->
</bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>
1.9.4 使用Qualifiers對基於註解的自動裝配調整
@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定義:
<?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>
- 具有
main
限定符值的Bean與構造函數參數連接,構造函數參數使用相同的值進行限定 - 帶有
action
限定符值的bean與構造函數參數連接,構造函數參數使用相同的值進行限定。
為了回退匹配,bean的名字是默認的限定符。因此,你可以定義bean的id為main替代嵌入的限定符元素,導致相同的匹配結果。然而,即使你能使用這個約定通過名字指定bean,@Autowired
基本上是關於帶有可選語義限定符的類型驅動的注入(意思是@Autowired
中帶有require限定符)。這意味著限定符值總是有縮小類型匹配集合的語義,即使bean名為回退名稱。他們從語義上不能表達引用一個唯一bean的id。好的限定符值是main
、EMEA
或persistent
,它們表示獨立於bean id
的特定組件的特徵,對於匿名bean定義(如前面示例中的定義),可以自動生成這些特徵。
限定符也能應用到類型化的集合,像前面討論的-例如,Set。在這種例子中,所有匹配的bean,根據聲明的限定符,作為一個集合被注入。這暗示限定符沒有必要是唯一的。相反,它們構成了過濾標準。例如,你可以定義具有相同限定符值action
的多個MovieCatalog Bean,所有這些都注入到以@Qualifier(“ action”)
註解的Set 中。
在類型匹配的候選者中,讓限定符值選擇目標bean名稱,在注入點不需要@Qualifier註解。如果沒有其他解析指示器(例如限定符或primary標記),對於非唯一依賴情況,Spring將注入點名(即字段名或參數名)與目標bean名相匹配,並選擇同名的候選項(如果有的話)。
也就是說,如果你打算試圖通過名稱表示註解驅動注入,不要主要地使用@Autowired,即使它有通過名稱在類型匹配的候選者間選擇的能力。相反,使用JSR-250@Resource
註解,它是語義地通過使用唯一名稱定義標識一個指定目標組件,聲明的類型與匹配過程無關。@Autowired有不同的語義:按類型選擇候選bean之後,指定的字符串限定符值僅在那些類型選擇的候選中被考慮(例如,匹配一個account
限定符與bean被標記相同的限定符標籤)。
對於定義為集合、Map、數組類型的bean,@Resource
是一個好的解決方案,通過唯一的名稱引用指定的集合或數組bean。也就是說,在Spring4.3以後你可以匹配一個Map和數組類型通過Spring的@Autowired
類型匹配算法,只要元素類型信息保留在@Bean返回類型簽名或集合繼承層次結構中。在這種場景中,你可以使用限定符值去相同類型集合選擇,如前一段所述。
在Spring4.3以後,@Autowired
還考慮了注入的自我引用(也就是說,引用回當前注入的bean)。注意自我注入是一種後備。對其他組件的常規依賴始終優先。從這個意義上說,自我推薦不參與常規的候選人選擇,因此尤其是絕不是主要的。相反,它們總是以最低優先級結束。實際上,你應該僅將自我引用用作最後的手段(例如,在相同實例上通過bean的事物代理調用其他方法)。在這種情況下,考慮將受影響的方法分解為單獨的委託Bean。或者,你可以使用@Resource
,它可以通過其唯一名稱獲取返回到當前bean的代理。
嘗試將
@Bean
方法的結果注入相同的配置類也是一種有效的自引用方案。要麼在實際需要的方法簽名中懶惰地解析此類引用(與配置類中的自動裝配字段相對),要麼將受影響的@Bean
方法聲明為靜態,將其與包含的配置類實例及其生命週期脫鉤。否則,僅在回退階段考慮此類Bean,而將其他配置類上的匹配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定義提供信息。你可以增加標籤的子標籤並且指定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>
在Classpath掃描和組件管理中,你可以看到在XML中基於註解另類的提供限定符元數據,查看Providing Qualifier Metadata with Annotations。
在某些場景中,使用註解不需要值可能就滿足。這是非常有用的,當註解應用更一般的用途和可以應用於幾種不同類型的依賴項時。例如,你可以提供一個脫機目錄,當沒有Internet連接可用時可以進行搜索。首先,定義相同的註解,類似下面的例子:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {
}
然後,增加註解到字段或者屬性去自動裝配,類似下面例子:
public class MovieRecommender {
@Autowired
@Offline //1
private MovieCatalog offlineCatalog;
// ...
}
- 這一行添加
@Offline
註解
接下來,bean的定義僅需要一個限定符type
,類似下面例子:
<bean class="example.SimpleMovieCatalog">
<qualifier type="Offline"/> //1
<!-- inject any dependencies required by this bean -->
</bean>
- 這個元素指定了限定符
你還可以定義自定義限定符註解,這些註解除了接受簡單value
屬性之外,還可以接受命名屬性。如果隨後在要自動裝配的字段或參數上指定了多個屬性值,則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
@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;
// ...
}
最後,bean定義應該包含匹配的限定符值。這個例子展示你可以使用bean元數據屬性替換元素。如果可用,元素及其屬性優先,但是如果不存在這樣的限定符,則自動裝配機制將回退到標記內提供的值,如下面示例中的最後兩個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>
1.9.5 使用泛型作為自定裝配限定
除了@Qualifier
註解,你也可以使用Java泛型類型作為隱式的限定。例如,假設你有下面的配置:
@Configuration
public class MyConfiguration {
@Bean
public StringStore stringStore() {
return new StringStore();
}
@Bean
public IntegerStore integerStore() {
return new IntegerStore();
}
}
假設前面的bean實現一個泛型接口,(也就是說,Store和Store),你可以使用@Autowire裝配Store接口並且泛型作為一個限定符,類似下面的例子:
@Autowired
private Store<String> s1; // <String> qualifier, injects the stringStore bean String限定符
@Autowired
private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean Integer限定符
當自動裝配集合、Map接口和數組的時泛型限定符也可以使用。下面的例子裝配Store泛型為integer的List:
// 注入所有泛型為<Integer> 的Store的bean
// Store<String> 的bean不會出現在List中
@Autowired
private List<Store<Integer>> s;
參考代碼:
com.liyong.ioccontainer.starter.XmlGenericsQualifierIocContainer
1.9.6 使用CustomAutowireConfigurer
CustomAutowireConfigurer
是一個BeanFactoryPostProcessor
,它允許你註冊你自己的自定義限定符註解類型,甚至他們不被註解Spring的@Qualifier
。下面的例子展示怎樣去使用CustomAutowireConfigurer
:
<bean id="customAutowireConfigurer"
class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
<property name="customQualifierTypes">
<set>
<value>example.CustomQualifier</value>
</set>
</property>
</bean>
AutowireCandidateResolver
通過下面確定自動裝配候選者:
- 每個bean定義的
autowire-candidate
值 - 在元素上任何
default-autowire-candidates
模式有效 -
@Qualifier
註解和任何自定義註解存在註冊CustomAutowireConfigurer
當多個bean限定符作為自動裝配候選者時,primary
的確定類似下面:如果候選者bean中有一個bean被定義primary
屬性值被設置為true,它將被選擇。
參考代碼:
com.liyong.ioccontainer.starter.XmlQualifierIocContainer
1.9.7 @Resource
注入
Spring也支持通過使用JSR-250註解(javax.annotation.Resource
)在字段或bean屬性Setter方法上注入。這是在JavaEE通用的模式,在JSF管理bean和JAX-WS端點。Spring提供這個模式去管理Spring的bean。
@Resource
採用一個名字屬性。默認情況下,Spring將該值解釋為要注入的Bean名稱。換而言之,它遵循通過名字的語義,如下所示:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource(name="myMovieFinder") //1
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
- 這行注入
@Resource
。
如果顯示指定名稱,這個默認名是從字段或Setter方法名獲取。如果是字段,則採用字段名稱。在使用setter方法的情況下,它採用bean屬性名稱。以下示例將把名為movieFinder
的bean注入其setter方法中:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
與註解一起提供的名稱被
CommonAnnotationBeanPostProcessor
所感知的ApplicationContext
解析為bean名稱。如果你顯示的配置Spring的SimpleJndiBeanFactory
,這個名字能通過JNDI解析。然而,我們推薦你依賴默認的行為並且使用Spring的JNDI能力去保留間接的級別。
在沒有明確指定名稱的@Resource
使用的特殊情況下(類似於@Autowired), @Resource會找到一個主類型匹配而不是一個特定的命名bean,並解析我們熟知的可解析依賴項:BeanFactory
、ApplicationContext
、 ResourceLoader
, ApplicationEventPublisher
和MessageSource
接口。
因此,在下面的例子中,customerPreferenceDao
字段首先查找bean名字為customerPreferenceDao
然後回退查找CustomerPreferenceDao
主要匹配的類型:
public class MovieRecommender {
@Resource
private CustomerPreferenceDao customerPreferenceDao;
@Resource
private ApplicationContext context; //1
public MovieRecommender() {
}
// ...
}
- context字段是基於已知的可解析依賴項類型:
ApplicationContext
注入的。
1.9.8 使用@Value
@Value
典型的應用去注入外部屬性:
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name}") String catalog) {
this.catalog = catalog;
}
}
下面是配置:
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }
application.properties配置
catalog.name=MovieCatalog
在這種情況下,catalog參數和字段將等於MovieCatalog值。
Spring提供了一個默認的寬鬆內嵌值解析器。它將嘗試去解析屬性值並且如果不能被解析,屬性名(例如,${catalog.name})
將被作為值注入。如果要嚴格控制不存在的值,則應聲明一個PropertySourcesPlaceholderConfigurer
bean,如以下示例所示:
@Configuration
public class AppConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
當使用JavaConfig配置
PropertySourcesPlaceholderConfigurer
,@Bean
方法必須是static。
如果任何${}
佔位符不能被解析,使用上面的配置能夠確保Spring初始化失敗。它也可以使用方法類似setPlaceholderPrefix
、setPlaceholderSuffix
或setValueSeparator
去自定義佔位符。
Spring Boot 通過默認的
PropertySourcesPlaceholderConfigurer
bean配置,它能從application.properties
和application.yml
文件獲取屬性。
Spring提供的內置轉換器支持允許自動處理簡單的類型轉換(例如,轉換為Integer或int)。多個逗號分隔的值能夠自動的轉換為String數組不需要額外的操作。
它也可能提供一個默認值類似下面:
@Component
public class MovieRecommender {
private final String catalog;
//defaultCatalog為默認值
public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
this.catalog = catalog;
}
}
Spring BeanPostProcessor
在使用ConversionService
處理將@Value
中的String值轉換為目標類型的過程。如果你想提供轉換支持為你自己的自定義類型,你可以提供你自己的ConversionService
bean實例類似下面例子:
@Configuration
public class AppConfig {
@Bean
public ConversionService conversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
conversionService.addConverter(new MyCustomConverter());
return conversionService;
}
}
當@Value
包含SpEl
表達式時候,這個值將被在運行時動態地計算,類似下面例子:
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
this.catalog = catalog;
}
}
SpEL
還可以使用更復雜的數據結構:
@Component
public class MovieRecommender {
private final Map<String, Integer> countOfMoviesPerCatalog;
public MovieRecommender(
@Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
}
}
參考代碼:
com.liyong.ioccontainer.starter.XmlValueIocContainer
1.9.9 使用@PostConstruct
和@PreDestroy
CommonAnnotationBeanPostProcessor
不僅僅識別@Resource
註解也能識別JSR-250生命週期註解:javax.annotation.PostConstruct
和javax.annotation.PreDestroy
。在Spring 2.5中引入的對這些註解的支持為初始化回調和銷燬回調中描述的生命週期回調機制提供了一種替代方法。CommonAnnotationBeanPostProcessor
在Spring的ApplicationContext
中被注入,在生命週期的同一點上(備註:同一類回調方法,比如:銷燬方法),與相應的Spring生命週期接口方法或顯式聲明的回調方法調用帶有其中一個註解的方法。在下面的例子中,在以下示例中,緩存在初始化時預先填充,並在銷燬時清除。
public class CachingMovieLister {
@PostConstruct
public void populateMovieCache() {
// 填充緩存
}
@PreDestroy
public void clearMovieCache() {
// 清除緩存
}
}
有關組合各種生命週期機制的詳細信息,查看組合生命週期機制。
類似
@Resource
一樣,@PostConstruct
和@PreDestroy
註解類型是從JDK6到JDK8的標準Java庫的一部分。然而,整個javax.annotation
包與JDK 9中的核心Java模塊分離,最終在JDK 11中刪除。如果需要,javax.annotation-api
可以通過Maven中央庫獲取,簡單地增加到應用的類路徑下面和其他庫一樣。參考代碼:
com.liyong.ioccontainer.starter.XmlLifecycleIocContainer
作者
個人從事金融行業,就職過易極付、思建科技、某網約車平臺等重慶一流技術團隊,目前就職於某銀行負責統一支付系統建設。自身對金融行業有強烈的愛好。同時也實踐大數據、數據存儲、自動化集成和部署、分佈式微服務、響應式編程、人工智能等領域。同時也熱衷於技術分享創立公眾號和博客站點對知識體系進行分享。
博客地址: http://youngitman.tech
CSDN: https://blog.csdn.net/liyong1028826685
微信公眾號:
技術交流群: