大數據

Spring 5 中文解析核心篇-IoC容器之基於註解的容器配置

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>

隱式的註冊後置處理器包括:

AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor, 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;
    }

    // ...
}

確保你的目標組件(例如,MovieCatalogCustomerPreferenceDao)是一致性的通過類型聲明,也就是使用基於@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方法上,建議使用@Autowiredrequired屬性,而建議使用@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。這些接口和它們的拓展接口,例如,ConfigurableApplicationContextResourcePatternResolver是自動地解析,沒有特殊的安裝需要。下面的例子主動注入一個ApplicationContext對象:

public class MovieRecommender {

    @Autowired
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}

@Autowired@Inject@Value@Resource註解是通過Spring的BeanPostProcessor實現處理的。這意味著你不能應用這些註解在你自己的BeanPostProcessorBeanFactoryPostProcessor類中。這些類型必須被通過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>
  1. 具有main限定符值的Bean與構造函數參數連接,構造函數參數使用相同的值進行限定
  2. 帶有action限定符值的bean與構造函數參數連接,構造函數參數使用相同的值進行限定。

為了回退匹配,bean的名字是默認的限定符。因此,你可以定義bean的id為main替代嵌入的限定符元素,導致相同的匹配結果。然而,即使你能使用這個約定通過名字指定bean,@Autowired基本上是關於帶有可選語義限定符的類型驅動的注入(意思是@Autowired中帶有require限定符)。這意味著限定符值總是有縮小類型匹配集合的語義,即使bean名為回退名稱。他們從語義上不能表達引用一個唯一bean的id。好的限定符值是mainEMEApersistent,它們表示獨立於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定義提供信息。你可以增加標籤的子標籤並且指定typevalue為匹配你的自定義限定符註解。這個類型是註解的全限定類名的匹配。或者,為方便起見,如果不存在名稱衝突的風險,則可以使用簡短的類名。下面例子展示兩種方式:

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

    // ...
}
  1. 這一行添加@Offline註解

接下來,bean的定義僅需要一個限定符type,類似下面例子:

<bean class="example.SimpleMovieCatalog">
    <qualifier type="Offline"/> //1
    <!-- inject any dependencies required by this bean -->
</bean>
  1. 這個元素指定了限定符

你還可以定義自定義限定符註解,這些註解除了接受簡單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;
    }
}
  1. 這行注入@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,並解析我們熟知的可解析依賴項:BeanFactoryApplicationContextResourceLoader, ApplicationEventPublisherMessageSource接口。

因此,在下面的例子中,customerPreferenceDao字段首先查找bean名字為customerPreferenceDao然後回退查找CustomerPreferenceDao主要匹配的類型:

public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context; //1

    public MovieRecommender() {
    }

    // ...
}
  1. 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初始化失敗。它也可以使用方法類似setPlaceholderPrefixsetPlaceholderSuffixsetValueSeparator去自定義佔位符。

Spring Boot 通過默認的PropertySourcesPlaceholderConfigurer bean配置,它能從application.propertiesapplication.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.PostConstructjavax.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

微信公眾號:

技術交流群:

Leave a Reply

Your email address will not be published. Required fields are marked *