一塊兒來讀官方文檔-----SpringIOC(08)

1.9。基於註解的容器配置
註解在配置Spring方面比XML更好嗎?

基於註解的配置的引入提出了一個問題,即這種方法是否比XML「更好」。
簡短的答案是「取決於狀況」。

長話短說,每種方法都有其優缺點,一般,由開發人員決定哪一種策略更適合他們。
因爲定義方式的不一樣,註解在聲明中提供了不少上下文,從而使配置更短,更簡潔。
可是,XML擅長鏈接組件而不接觸其源代碼或從新編譯它們。

一些開發人員更喜歡將佈線放置在靠近源的位置,而另外一些開發人員則認爲帶註解的類再也不是POJO,
並且,該配置變得分散且難以控制。

不管選擇如何,Spring均可以容納兩種樣式,甚至能夠將它們混合在一塊兒。
值得指出的是,經過其JavaConfig選項,Spring容許以非侵入方式使用註解,
而無需接觸目標組件的源代碼。

註解是XML配置的替代方法,該配置依賴字節碼元數據來鏈接組件,而不是尖括號聲明。
經過使用相關的 類,方法或字段 聲明上的註解,開發人員無需使用XML來描述bean的鏈接,而是將配置移入組件類自己。java

如示例中所述:將RequiredAnnotationBeanPostProcessor,經過BeanPostProcessor的方式與註解結合使用是擴展Spring IoC容器的經常使用方法。web

例如,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配置將覆蓋經過註解注入的屬性spring

與往常同樣,您能夠根據類名將它們註冊爲單獨的bean定義,但也能夠經過在基於XML的Spring配置中包含如下標記來隱式註冊它們:api

<?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。spring-mvc

上邊的這段話意思不是很明確,須要解釋一下之前用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屬性,容器將拋出異常。tomcat

這樣顯式的失敗,避免了實例在應用的時候出現NullPointerException的狀況。數據結構

@Required註解在Spring Framework 5.1時正式被棄用,
Spring更贊同使用構造函數注入來進行必需參數的設置
(或者使用InitializingBean.afterPropertiesSet()的自定義實現來進行bean屬性的設置)。
1.9.2。@Autowired
在本節包含的示例中,JSR330的@Inject註釋能夠用來替代Spring的@Autowired註釋。

您能夠將@Autowired註解應用於構造函數,如如下示例所示:mvc

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 class="example.SimpleMovieCatalog">
        <qualifier value="main"/> <!-- 指定qualifier屬性 -->
    </bean>

</beans>

bean名稱被認爲是默認的qualifier值。
也能夠不使用qualifier而是將該bean的id定義爲main,達到相同的匹配效果。

然而,儘管您可使用這種約定來按名稱引用特定的bean,但@Autowired基本上是關於類型驅動的注入,qualifier只是在類型之上的可選選項,這意味着,即便使用了bean名稱來進行qualifier的限定,qualifier 值也老是在類型匹配集中選擇相同名稱的bean。

qualifier 也適用於collections, 如前所述—例如 Set<MovieCatalog>,在這種狀況下,根據聲明的qualifier值,全部匹配的bean都做爲一個集合注入。
這意味着qualifier沒必要是唯一的。相反,它們構成了過濾標準。例如,您能夠定義具備相同qualifier值「action」的多個MovieCatalog bean,
全部這些bean都被注入到帶有@Qualifier(「action」)註釋的集合中。

public class ServiceController {

	@Autowired
	@Qualifier("main")
	private List<MovieCatalog> serviceList;
}

<bean class="example.SimpleMovieCatalogOne">
        <qualifier value="main"/> <!-- 指定相同的qualifier屬性 -->
</bean>

<bean class="example.SimpleMovieCatalogTwo">
        <qualifier value="main"/> <!-- 指定相同的qualifier屬性 -->
</bean>

<bean class="example.SimpleMovieCatalogThree">
        <qualifier value="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定義提供信息。您能夠添加<qualifier></qualifier>標記做爲<bean></bean>標記的子元素,而後指定類型和值來匹配您的自定義qualifier註解。該類型與註釋的全限定類名匹配。
另外,爲了方便起見,若是不存在名稱衝突的風險,您可使用簡短的類名。
下面的例子演示了這兩種方法:

<?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>

在某些狀況下,使用沒有值的註解就足夠了。當註解用於更通用的目的,而且能夠跨幾種不一樣類型的依賴項應用時,這一點很是有用。例如,您能夠提供一個脫機目錄,當沒有可用的Internet鏈接時能夠搜索該目錄。首先,定義簡單註釋,以下例所示:

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {

}

而後將註解添加到要自動裝配的字段或屬性,如如下示例所示:

public class MovieRecommender {

    @Autowired
    @Offline 
    private MovieCatalog offlineCatalog;

    // ...
}

如今,bean定義僅須要一個限定符type,如如下示例所示:

<bean class="example.SimpleMovieCatalog">
    <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元屬性來代替<qualifier></qualifier>元素。
若是可用,<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>
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<String>和 Store<Integer>)

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"
        class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
    <property name="customQualifierTypes">
        <set>
            <value>example.CustomQualifier</value>
        </set>
    </property>
</bean>

example.CustomQualifier Spring會根據這個類路徑加載這個類,
並將這個類做爲和@Qualifier同做用來對待

自動注入是如何處理候選對象的?

  • bean definition 的 autowire-candidate 值,值爲false表示該bean不參於候選
  • <beans/>元素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獲取,只需像其餘任何庫同樣將其添加到應用程序的類路徑中便可。
相關文章
相關標籤/搜索