1. 程式人生 > 實用技巧 >一起來讀官方文件-----SpringIOC(08)

一起來讀官方文件-----SpringIOC(08)

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 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,在這種情況下,根據宣告的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註解。該型別與註釋的全限定類名匹配。
另外,為了方便起見,如果不存在名稱衝突的風險,您可以使用簡短的類名。
下面的例子演示了這兩種方法:

<?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元屬性來代替元素。
如果可用,元素及其屬性優先,但是如果沒有這樣的限定符,自動裝配機制就會回到標籤中提供的值上,就像下面例子中的最後兩個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

    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不參於候選
  • 元素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 釋出!