1.9,基于注解的容器配置
注解在配置Spring方面比XML更好嗎?
基于注解的配置的引入提出了一個問題,即這種方法是否比XML“更好”,
簡短的答案是“取決于情況”,
長話短說,每種方法都有其優缺點,通常,由開發人員決定哪種策略更適合他們,
由于定義方式的不同,注解在宣告中提供了很多背景關系,從而使配置更短,更簡潔,
但是,XML擅長連接組件而不接觸其源代碼或重新編譯它們,
一些開發人員更喜歡將布線放置在靠近源的位置,而另一些開發人員則認為帶注解的類不再是POJO,
而且,該配置變得分散且難以控制,
無論選擇如何,Spring都可以容納兩種樣式,甚至可以將它們混合在一起,
值得指出的是,通過其JavaConfig選項,Spring允許以非侵入方式使用注解,
而無需接觸目標組件的源代碼,
注解是XML配置的替代方法,該配置依賴位元組碼元資料來連接組件,而不是尖括號宣告,
通過使用相關的 類,方法或欄位 宣告上的注解,開發人員無需使用XML來描述bean的連接,而是將配置移入組件類本身,
如示例中所述:將RequiredAnnotationBeanPostProcessor,通過BeanPostProcessor的方式與注解結合使用是擴展Spring IoC容器的常用方法,
例如,Spring 2.0引入了使用@Required注解強制執行必需屬性的可能性,
Spring 2.5引入@Autowired注解,提供的功能與自動裝配協作器中所述的功能相同,但具有更細粒度的控制和更廣泛的適用性,
Spring 2.5還添加了對JSR-250批注(例如 @PostConstruct和@PreDestroy)的支持,
Spring 3.0增加了對javax.inject包中包含的JSR-330(Java依賴性注入)注解的支持,例如@Inject 和@Named,
注解注入在XML注入之前執行,因此,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
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>
<context:annotation-config/> 隱式注冊后置處理器包括 :
AutowiredAnnotationBeanPostProcessor
CommonAnnotationBeanPostProcessor
PersistenceAnnotationBeanPostProcessor
RequiredAnnotationBeanPostProcessor
并且當使用<context:component-scan/>后,即可將<context:annotation-config/>省去
context:annotation-config/只在定義它的相同應用程式背景關系中查找關于bean的注解,
這意味著,如果你把context:annotation-config/定義在WebApplicationContext的DispatcherServlet中,它只是檢查controllers中的@Autowired注解,而不是你的services,
上邊的這段話意思不是很明確,需要解釋一下以前用web.xml配置時的Spring啟動流程
拿出幾段配置
<!--配置開始 -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/service/*</url-pattern>
</servlet-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:spring/spring-base.xml
</param-value>
</context-param>
<!--配置結束 -->
上邊的配置應該是多年前webi應用的基礎配置,理一下tomcat啟動后如何呼叫Spring的大概流程
1. tomcat讀取web.xml檔案(此處不管tomcat如何找到xml),決議內容并分組,
分成ServletContainerInitializer,servlet,listener,context-param等多個陣列
2.逐個進行決議,先決議ServletContainerInitializer
//這個就相當典型了 這個東西就是之前的文章講過的ServletContainerInitializer
//Tomcat啟動會查找ServletContainerInitializer實作類并執行其中的onStartup方法,
//Spring-web模塊存在ServletContainerInitializer實作類,所以Tomcat啟動會呼叫Spring-web的代碼,
//但是我們用Spring框架的話不需要實作這個介面,實作一個Spring的介面WebApplicationInitializer,
//就可以由Tomcat呼叫Spring-web的ServletContainerInitializer實作類
Iterator i$ = this.initializers.entrySet().iterator();
while(i$.hasNext()) {
Entry entry = (Entry)i$.next();
try {
((ServletContainerInitializer)entry.getKey()).onStartup((Set)entry.getValue(), this.getServletContext());
} catch (ServletException var22) {
log.error(sm.getString("standardContext.sciFail"), var22);
ok = false;
break;
}
}
但是這里我們并沒有用這種方式而是用了listener的方式繼續往下看
3. 決議listener,這里this.listenerStart()會決議我們配置的ContextLoaderListener
if (ok && !this.listenerStart()) {
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
就在這里tomcat關聯上了Spring的ApplicationContext,會實體化XmlWebApplicationContext,
實體化時取出context-param中的name為contextConfigLocation的組態檔,來進行決議注冊
4.決議servlet,this.loadOnStartup(this.findChildren())來決議servlet,
if (ok && !this.loadOnStartup(this.findChildren())) {
log.error(sm.getString("standardContext.servletFail"));
ok = false;
}
這里就會進入DispatcherServlet的init方法,
init方法中會根據當前的ServletContext查找在此之前有沒有初始化過Spring的ApplicationContext,
然后再判斷當前DispatcherServlet有沒有ApplicationContext,
如果沒有就初始化一個并把之前初始化ApplicationContext的設定為父節點
總結一下,也就是說用了上邊的配置的話,tomcat在啟動程序中,會初始化兩遍并生成兩個ApplicationContext物件,
第一遍決議context-param中param-name 為contextConfigLocation的組態檔,
并以此組態檔生成一個ApplicationContext ROOT
第二遍是決議DispatcherServlet servlet的spring-mvc.xml組態檔,
再以此組態檔生成一個ApplicationContext,并將ROOT設定為父節點
因此就產生了一個問題,當你在兩個ApplicationContext都可以掃描到同一個Bean的時候,
那么這個bean在連個ApplicationContext中都各存在一個實體,并且實體不一樣
舉一個之前遇到的問題:
之前想給某個controller加一個AOP,攔截某些方法進行特殊處理,但是我把 <aop:aspectj-autoproxy/>這個注解
放到了下面這個層次的組態檔中了
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:spring/spring-base.xml
</param-value>
</context-param>
最后我的AOP并沒有生效,后來又把注解挪到了spring-mvc.xml中,才生效,
之前百度搜到說:spring-mvc 的配置掃描優先于spring的組態檔
通過除錯才理解這句話:
我的controller在spring的ApplicationContext中有一份被AOP代理的物件
在spring-mvc的ApplicationContext中還有一份沒被代理的普通物件
因為spring-mvc配置加載的晚,所以用到的都是沒有被代理的物件
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的情況,
@Required注解在Spring Framework 5.1時正式被棄用,
Spring更贊同使用建構式注入來進行必需引數的設定
(或者使用InitializingBean.afterPropertiesSet()的自定義實作來進行bean屬性的設定),
1.9.2,@Autowired
在本節包含的示例中,JSR330的@Inject注釋可以用來替代Spring的@Autowired注釋,
您可以將@Autowired注解應用于建構式,如以下示例所示:
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
從Spring Framework 4.3開始,@Autowired如果目標bean僅定義一個建構式作為開始,則不再需要在此類建構式上進行注解,
但是,如果有多個建構式可用,并且沒有主/默認建構式,則必須至少注解一個建構式,@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;
}
// ...
}
確保目標組件(例如MovieCatalog或CustomerPreferenceDao)與帶@Autowired注解的注入點的型別一致地宣告,
否則,注入可能會由于運行時出現“no type match found”錯誤而失敗,
對于通過類路徑掃描找到的xml定義的bean或組件類,容器通常預先知道具體的型別,
但是,對于@Bean工廠方法,您需要確保宣告的回傳型別具有足夠的表達能力,
對于實作多個介面的組件,或者對于可能由其實作型別參考的組件,
考慮在您的工廠方法上宣告最特定的回傳型別(至少與參考您的bean的注入點所要求的那樣特定),
您還可以將@Autowired注解添加到需要該型別陣列的欄位或方法中,指示Spring提供特定型別的所有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注解,
否則,它們的順序將遵循容器中相應目標bean定義的注冊順序,
您可以使用@Order在目標類級別和@Bean方法上宣告注解,
@Order值可能會影響注入點的優先級,但請注意它們不會影響單例啟動順序,
這是由依賴關系和@DependsOn宣告確定的正交關系,(舉例:a,b,c三個bean設定的order分別為1,2,3,
但是a依賴c,所以a在初始化的時候會初始化c,導致c比b提前初始化)
請注意,標準javax.annotation.Priority注解在該@Bean級別不可用 ,因為無法在方法上宣告它,
可以通過將@Order值與@Primary每個型別的單個bean結合使用來對其語意進行建模,
即使是型別化的Map實體也可以自動注入,鍵包含相應的bean名稱是String型別,值是對應的bean實體,如下面的示例所示:
public class MovieRecommender {
private Map<String, MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
}
默認情況下,當給定注入點沒有匹配的候選bean可用時,自動裝配將失敗,對于宣告的陣列,集合或映射,至少應有一個匹配元素,
默認是將帶注解的方法和欄位視為必須要注入的依賴項,
你可以通過標記為非必需注入來改變這個行為(例如,通過在@Autowired中設定required屬性為false):
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired(required = false)
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
@Autowired(required = false)用在方法上時
當存在任何一個引數不可注入,則根本不會呼叫該方法,
在這種情況下,完全不需要填充非必需欄位,而保留其默認值,
當方法有多個引數時,可以使用該注解標識其中的某個引數可以不被注入
public class ServiceController {
private ServiceTwo serviceTwo;
private CusService serviceOne;
public ServiceController(CusService cusService,
@Autowired(required = false) ServiceTwo serviceTwo){
this.serviceOne = cusService;
this.serviceTwo = serviceTwo;
}
}
在任何給定bean類中,只有一個建構式可以宣告@Autowired,并將required屬性設定為true,以指示當作為Spring bean使用時要自動裝配的建構式,
因此,如果required屬性的默認值為true,那么只有一個建構式可以使用@Autowired注解,
如果有多個建構式宣告注解,那么它們都必須宣告required=false,才能被認為是自動裝配的候選者(類似于XML中的autowire=constructor),
通過在Spring容器中匹配bean可以滿足的依賴關系最多的建構式將被選擇,
如果沒有一個候選函式可以滿足,那么將使用主/默認建構式(如果存在),
類似地,如果一個類宣告了多個建構式,但是沒有一個是用@Autowired注解的,那么一個主/默認建構式(如果有的話)將會被使用,
如果一個類只宣告了一個建構式,那么它將始終被使用,即使沒有@Autowired注解,
請注意,帶注解的建構式不必是public的,
建議在setter方法上的已棄用的@Required注釋上使用@Autowired屬性,
將required屬性設定為false表示該屬性對于自動裝配目的是不需要的,并且如果該屬性不能自動裝配,則忽略它,
另一方面,@Required更強制,因為它強制用容器支持的任何方法設定屬性,如果沒有定義值,則會引發相應的例外,
另外,您可以通過Java8來表達特定依賴項的非必需性質java.util.Optional,如以下示例所示:
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(Optional<MovieFinder> movieFinder) {
...
}
}
從Spring Framework 5.0開始,您還可以使用@Nullable注解(任何包中的Nullable注解,例如,javax.annotation.Nullable來自JSR-305的注解),
使用此注解標識該引數不一定會被注入,有可能會是空值
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(@Nullable MovieFinder movieFinder) {
...
}
}
您還可以對這些介面(BeanFactory,ApplicationContext,Environment,ResourceLoader, ApplicationEventPublisher,和MessageSource)使用@Autowired,
這些介面及其擴展介面(例如ConfigurableApplicationContext或ResourcePatternResolver)將自動決議,而無需進行特殊設定,
以下示例自動裝配ApplicationContext物件:
public class MovieRecommender {
@Autowired
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
在@Autowired,@Inject,@Value,和@Resource注解由Spring注冊的BeanPostProcessor實作,
這意味著您不能在自己的型別BeanPostProcessor或BeanFactoryPostProcessor型別(如果有)中應用這些注解,
必須通過使用XML或Spring@Bean方法顯式地“連接”這些型別,
不僅相當上一章的內容:
您應該看到一條參考性日志訊息:
Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying),
這條訊息的意思大概就是說這個bean沒有得到所有BeanPostProcessor的處理
如果您自定義的BeanPostProcessor或BeanFactoryPostProcessor在自動注入的BeanPostProcessor之前實體化那么就無法為您注入您想要的引數,
1.9.3,@Primary
由于按型別自動布線可能會導致多個候選物件,因此通常有必要對選擇程序進行更多控制,
一種實作此目的的方法是使用Spring的 @Primary注解,
@Primary指示當多個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;
// ...
}
1.9.4,@Qualifier
@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 >
<qualifier value="https://www.cnblogs.com/dabaieyangzhijidi/p/main"/> <!-- 指定qualifier屬性 -->
</bean>
</beans>
bean名稱被認為是默認的qualifier值,
也可以不使用qualifier而是將該bean的id定義為main,達到相同的匹配效果,
然而,盡管您可以使用這種約定來按名稱參考特定的bean,但@Autowired基本上是關于型別驅動的注入,qualifier只是在型別之上的可選選項,這意味著,即使使用了bean名稱來進行qualifier的限定,qualifier 值也總是在型別匹配集中選擇相同名稱的bean,
qualifier 也適用于collections,
如前所述—例如 Set
這意味著qualifier不必是惟一的,相反,它們構成了過濾標準,例如,您可以定義具有相同qualifier值“action”的多個MovieCatalog bean,
所有這些bean都被注入到帶有@Qualifier(“action”)注釋的集合中,
public class ServiceController {
@Autowired
@Qualifier("main")
private List<MovieCatalog> serviceList;
}
<bean >
<qualifier value="https://www.cnblogs.com/dabaieyangzhijidi/p/main"/> <!-- 指定相同的qualifier屬性 -->
</bean>
<bean >
<qualifier value="https://www.cnblogs.com/dabaieyangzhijidi/p/main"/> <!-- 指定相同的qualifier屬性 -->
</bean>
<bean >
<qualifier value="https://www.cnblogs.com/dabaieyangzhijidi/p/action"/> <!-- 指定相同的qualifier屬性 -->
</bean>
如果沒有其他注解(例如qualifier或primary ),
對于非唯一依賴情況,Spring將注入點名稱(即欄位名稱或引數名稱)與目標bean名稱或者bean id匹配,
并選擇同名的候選物件(如果有同名的的話,沒有同名的話則依然拋出例外),
如果您打算通過名稱進行依賴注入,請不要主要使用@Autowired,即使它能夠通過bean名稱在型別匹配的候選者中進行選擇,
使用JSR-250 @Resource注解:
1. 如果同時指定了name和type,按照bean Name 和 bean 型別同時匹配
2. 如果指定了name,就按照bean Name 匹配
3. 如果指定了type,就按照型別匹配
4. 如果既沒有指定name,又沒有指定type,就先按照beanName匹配;
如果沒有匹配,再按照型別進行匹配;
測驗 @Resource的時候還發現一個有意思的東西,
當被注入的是個List的時候,不管是什么型別的List,
只要@Resource加了name條件,都能被注入進去,
比如 List<String> 會被注入到List<MovieCatalog> 大家可以試一下
@Autowired注解:
在按型別選擇候選bean之后,再在候選者bean中選擇相同名稱的,
@Autowired適用于 欄位,構造方法,和多引數方法,允許通過qualifier注解在引數級別上縮小范圍,
相比之下,@Resource只支持具有單個引數的欄位和bean屬性設定器方法,
因此,如果注入目標是建構式或多引數方法,則應該堅持使用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定義提供資訊,您可以添加
另外,為了方便起見,如果不存在名稱沖突的風險,您可以使用簡短的類名,
下面的例子演示了這兩種方法:
<?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 >
<qualifier type="Genre" value="https://www.cnblogs.com/dabaieyangzhijidi/p/Action"/>
<!-- inject any dependencies required by this bean -->
</bean>
<bean >
<qualifier type="example.Genre" value="https://www.cnblogs.com/dabaieyangzhijidi/p/Comedy"/>
<!-- inject any dependencies required by this bean -->
</bean>
<bean id="movieRecommender" />
</beans>
在某些情況下,使用沒有值的注解就足夠了,當注解用于更通用的目的,并且可以跨幾種不同型別的依賴項應用時,這一點非常有用,例如,您可以提供一個脫機目錄,當沒有可用的Internet連接時可以搜索該目錄,首先,定義簡單注釋,如下例所示:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {
}
然后將注解添加到要自動裝配的欄位或屬性,如以下示例所示:
public class MovieRecommender {
@Autowired
@Offline
private MovieCatalog offlineCatalog;
// ...
}
現在,bean定義僅需要一個限定符type,如以下示例所示:
<bean >
<qualifier type="Offline"/>
<!-- inject any dependencies required by this bean -->
</bean>
您還可以定義自定義qualifier注解,自定義的注解可以定義除了簡單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
}
要自動裝配的欄位將用自定義qualifier進行注解,并包括這兩個屬性的值: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元屬性來代替
如果可用,
<?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 >
<qualifier type="MovieQualifier">
<attribute key="format" value="https://www.cnblogs.com/dabaieyangzhijidi/p/VHS"/>
<attribute key="genre" value="https://www.cnblogs.com/dabaieyangzhijidi/p/Action"/>
</qualifier>
<!-- inject any dependencies required by this bean -->
</bean>
<bean >
<qualifier type="MovieQualifier">
<attribute key="format" value="https://www.cnblogs.com/dabaieyangzhijidi/p/VHS"/>
<attribute key="genre" value="https://www.cnblogs.com/dabaieyangzhijidi/p/Comedy"/>
</qualifier>
<!-- inject any dependencies required by this bean -->
</bean>
<bean >
<meta key="format" value="https://www.cnblogs.com/dabaieyangzhijidi/p/DVD"/>
<meta key="genre" value="https://www.cnblogs.com/dabaieyangzhijidi/p/Action"/>
<!-- inject any dependencies required by this bean -->
</bean>
<bean >
<meta key="format" value="https://www.cnblogs.com/dabaieyangzhijidi/p/BLURAY"/>
<meta key="genre" value="https://www.cnblogs.com/dabaieyangzhijidi/p/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
class StringStore implements Store<String>{
}
class IntegerStore implements Store<Integer>{
}
則可以@Autowire將該Store介面和通用用作限定符,如以下示例所示:
@Autowired
private Store<String> s1; // <String> qualifier, 注入 stringStore bean
@Autowired
private Store<Integer> s2; // <Integer> qualifier, 注入 the integerStore bean
在自動裝配串列,Map實體和陣列時,通用限定符也適用,下面的示例自動連接泛型List:
// 只注入 Store<Integer> 型別
// Store<String> 不會被注入
@Autowired
private List<Store<Integer>> s;
1.9.6,使用CustomAutowireConfigurer
CustomAutowireConfigurer 是一個BeanFactoryPostProcessor
您可以注冊自己的自定義限定符注解型別,即使它們未使用Spring的@Qualifier注解進行注解,
像之前我們定義的注解
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {
String value();
}
這種寫法主要就是托@Qualifier的福氣
但我們也可以不依賴它
以下示例顯示如何使用CustomAutowireConfigurer:
<bean id="customAutowireConfigurer"
>
<property name="customQualifierTypes">
<set>
<value>example.CustomQualifier</value>
</set>
</property>
</bean>
example.CustomQualifier Spring會根據這個類路徑加載這個類,
并將這個類作為和@Qualifier同作用來對待
自動注入是如何處理候選物件的?
- bean definition 的 autowire-candidate 值,值為false表示該bean不參于候選
元素default-autowire-candidates上可用的任何模式,值為false表示該組的bean不參與候選 - @Qualifier注解 和 任何在customautowiresfigurer注冊的自定義注解的存在會被優先使用
當多個bean符合自動裝配候選條件時,
確定“primary”的步驟如下:如果候選中恰好有一個bean定義將primary屬性設定為true,則將其選中,
1.9.7,@Resource
Spring還通過在欄位或bean屬性設定器方法上使用JSR-250@Resource批注(javax.annotation.Resource)支持注入,
1. 如果同時指定了name和type,按照bean Name 和 bean 型別同時匹配
2. 如果指定了name,就按照bean Name 匹配
3. 如果指定了type,就按照型別匹配
4. 如果既沒有指定name,又沒有指定type,就先按照beanName匹配;
如果沒有匹配,再按照型別進行匹配;
@Resource具有name屬性,默認情況下,Spring將該值解釋為要注入的Bean名稱,
換句話說,它遵循名稱語意,如以下示例所示:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource(name="myMovieFinder")
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
如果未明確指定名稱,則默認名稱是從欄位名稱或setter方法派生的,
如果是欄位,則采用欄位名稱,
在使用setter方法的情況下,它采用bean屬性名稱,
以下示例將名為 movieFinder的bean注入到setter方法:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
因此,在下例中,customerPreferenceDao欄位首先查找名為“customerPreferenceDao”的bean,找不到的話然后回傳到與型別customerPreferenceDao匹配的bean:
public class MovieRecommender {
@Resource
private CustomerPreferenceDao customerPreferenceDao;
@Resource
private ApplicationContext context;
public MovieRecommender() {
}
}
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} 則將被當做值注入到屬性中,
例如:catalog="${catalog.name}"
如果要嚴格控制不存在的值,則應宣告一個PropertySourcesPlaceholderConfigurerbean,如以下示例所示:
@Configuration
public class AppConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
當配置PropertySourcesPlaceholderConfigurer使用JavaConfig,該@Bean方法必須是static,
如果${} 無法決議任何占位符,則使用上述配置可確保Spring初始化失敗,
默認情況下,Spring Boot配置一個PropertySourcesPlaceholderConfigurer
從application.properties和application.yml獲取bean的屬性,
Spring提供的內置轉換器支持允許自動處理簡單的型別轉換(例如轉換為Integer 或轉換為簡單的型別int),
多個逗號分隔的值可以自動轉換為String陣列,而無需付出額外的努力,
可以像下邊一樣提供默認值:
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
this.catalog = catalog;
}
}
Spring BeanPostProcessor在后臺使用ConversionService來處理將@Value中的字串值轉換為目標型別的程序,如果你想為自己的自定義型別提供轉換支持,你可以提供自己的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;
}
}
1.9.9,使用@PostConstruct和@PreDestroy
CommonAnnotationBeanPostProcessor不僅處理@Resource注解
也處理javax.annotation.PostConstruct和 javax.annotation.PreDestroy,
在Spring 2.5中引入了對這些注解的支持,為初始化回呼和銷毀回呼中描述的生命周期回呼機制提供了一種替代方法,
如果在Spring ApplicationContext中注冊了CommonAnnotationBeanPostProcessor,帶有這兩個注解的方法將會被回呼執行,
在下面的例子中,快取在初始化時被預填充,在銷毀時被清除:
public class CachingMovieLister {
@PostConstruct
public void populateMovieCache() {
// populates the movie cache upon initialization...
}
@PreDestroy
public void clearMovieCache() {
// clears the movie cache upon destruction...
}
}
像@Resource一樣,@PostConstruct和@PreDestroy注解是JDK6到8的標準Java庫的一部分,
但是,整個javax.annotation 程式包都與JDK 9中的核心Java模塊分開,并最終在JDK 11中洗掉了,
如果需要,需要對javax.annotation-api工件進行處理,
現在可以通過Maven Central獲取,只需像其他任何庫一樣將其添加到應用程式的類路徑中即可,
本文由博客一文多發平臺 OpenWrite 發布!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/172118.html
標籤:Java
上一篇:打工四年總結的資料庫知識點
