天天看點

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>           

隐式的注冊後置處理器包括:

AutowiredAnnotationBeanPostProcessor

CommonAnnotationBeanPostProcessor

,

PersistenceAnnotationBeanPostProcessor

和前面提到的

RequiredAnnotationBeanPostProcessor

<context:annotation-config />

僅在定義它的相同應用程式上下文中查找關于bean的注解。意思是,如果你在

WebApplicationContext

中為

DispatcherServlet

配置

< context:annotation-config/>

,它僅僅檢查

@Autowired

在你的Controller層并且不會在你的Service層。檢視 DispatcherServlet 更多資訊。

1.9.1 @Required

@Required

注解應用到bean屬性的Setter方法,類似下面例子:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Required
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}           

這個注解表示bean屬性在運作時必須被填充指派,在一個bean定義中顯示指派或自動裝配。如果受影響的bean屬性沒有被填充值容器抛出一個異常。這允許更早的顯示異常,避免以後再出現

NullPointerException

執行個體等。我們仍然推薦你把斷言放入到bean類本身中(例如,放入初始化方法)。這樣做會強制執行那些必需的引用和值,即使你在容器外部使用該類也是如此。

@Required

注解在Spring5.1中被正式的不推薦使用,更好的方式是,使用構造函數注入要求的設定(或

InitializingBean.afterPropertiesSet()

的自定義實作以及bean屬性設定器方法)。

1.9.2 @Autowired

JSR 330的

@Inject

注解能夠使用在Spring的

@Autowired

注解的地方。

可以在構造方法上使用

@Autowired

,類似下面例子:

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}           
從Spring4.3開始,如果目标bean僅僅定義以一個構造函數開始,那麼構造函數上的

@Autowired

注解是不必要的。然而,如果有一些構造函數是有效的并且沒有主要預設的構造函數,則至少有一個構造函數必須被注解為

@Autowired

,目的是引導容器選擇其中一個去使用。檢視 構造方法解析 詳情。

你也可以應用@Autowired注解到傳統的Setter方法,類似下面例子:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}           

你可以應用

@Autowired

注解到任意方法名和多個參數,類似下面例子:

public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(MovieCatalog movieCatalog,
            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}           

@Autowired

注解到字段甚至混合到構造方法,類似下面例子:

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    private MovieCatalog movieCatalog;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}           
確定你的目标元件(例如,

MovieCatalog

CustomerPreferenceDao

)是一緻性的通過類型聲明,也就是使用基于

@Autowired

注入點。否則,注入可能由于在運作時出現“找不到類型比對”錯誤而失敗。

對于基于XML定義的bean或者元件類通過類路徑掃描,容器通常知道具體的類型。然而,對于

@Bean

工廠方法,你需要確定聲明的傳回類型足夠的表達。對于實作多個接口的元件,或者對于實作類型可能引用的元件,請考慮在工廠方法中聲明最具體的傳回類型。至少與指向bean的注入點所要求的一樣具體(備注:盡可能傳回具體的實作類型)。

你還可以引導Spring從

ApplicationContext

中提供特定類型的所有bean,通過将

@Autowired

注解添加到需要該類型數組的字段或方法中,類似下面例子:

public class MovieRecommender {

    @Autowired
    private MovieCatalog[] movieCatalogs;

    // ...
}           

類型化集合也是如此,類似下面例子:

public class MovieRecommender {

    private Set<MovieCatalog> movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...
}           
如果你想數組或集合中的元素在指定的順序下被排序,那麼你的目标bean可以實作

org.springframework.core.Ordered

接口、

@Order

或标準的

@Priority

注解。否則,它們的順序遵循在容器中對應目标bean定義注冊的順序。

你可以在目标類級别和在

@Bean

方法上申明

@Order

注解,可能是針對單個bean定(多個定義情況下使用相同的bean類)。

@Order

值可能會影響注入點的優先級,不會影響單例bean的啟動順序,這是由依賴關系和

@DependsOn

聲明确定。

注意:标準的

javax.annotation.Priority

注解在

@Bean

級别是無效的,因為它不能被聲明在方法上。它的語義可以通過

@Order

值與

@Primary

結合在每種類型的單個bean上。

甚至類型Map執行個體也能被自動裝配隻要期望的key類型是String。map的值包含了所有期望類型的bean,并且這些key包含對應bean的名稱,像下面的例子展示:

public class MovieRecommender {

    private Map<String, MovieCatalog> movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...
}           

預設情況下,當沒有比對的候選bean對應給定的注入點的時候自動裝配将失敗。在這種情況下申明數組、集合或map至少有一個期望比對的元素。

預設行為是将帶注解的方法和字段視為訓示所需的依賴項。你可以改變這種行為,像下面這個例子,通過将架構标記為不需要,進而使架構可以跳過不滿意的注入點(通過在

@Autowired

屬性中的

required

設定為false)。說明:滿足注意條件就注入,不滿足條件就跳過注入。

public class SimpleMovieLister {

    private MovieFinder movieFinder;
        //自動注入不滿足就不注入
    @Autowired(required = false)
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...
}           

如果一個非必需的方法的依賴項(或者它的一個依賴項,在有多個參數的情況下)不可用,那麼這個方法将不會被調用。在這種情況下,完全不需要填充非必需字段,将其預設值保留在适當的位置。注入構造函數和工廠方法參數是一種特殊情況,因為由于Spring的構造函數解析算法可能要處理多個構造函數,

@Autowired

中的

required

屬性有一些不同的含義。

注入的構造函數和工廠方法參數是一種特殊情況,因為由于Spring的構造函數解析算法可能會處理多個構造函數,是以

@Autowired

中必填屬性的含義有所不同。預設情況下,有效地需要構造函數和工廠方法參數,但是在單構造函數場景中有一些特殊規則,例如,如果沒有比對的Bean可用,則将多元素注入點(數組,集合,映射)解析為空執行個體。這允許一種通用的實作模式,其中所有依賴項都可以在唯一的多參數構造函數中聲明-例如,申明一個簡單的公共構造函數不需要

@Autowired

注解。

任何給定的bean類隻有一個構造函數可以聲明

@Autowired

并設定屬性

required

為true,訓示當使用Spring Bean時構造函數自動裝配。是以,如果必填屬性保留為預設值true,則隻能使用

@Autowired

注解單個構造函數。如果有多個構造函數聲明注解,那麼它們都必須聲明

required=false

,以便被認為是自動裝配的候選對象(類似于XML中的

autowire=constructor

)。将選擇通過比對Spring容器中的bean可以滿足的依賴關系數量最多的構造函數如果沒有一個候選者滿意,則将使用主要的/預設構造函數(如果存在)。同樣,如果一個類聲明了多個構造函數,但都沒有使用

@Autowired

進行注解,則将使用主要的/預設構造函數(如果存在)。如果一個類僅聲明一個單一的構造函數開始,即使沒有注釋,也将始終使用它。請注意,帶注解的構造函數不必是公共的。

在setter方法上,建議使用

@Autowired

required

屬性,而建議使用

@Required

注解。設定

required

屬性為false則表明這個屬性不是必須的對于自動裝配意圖,并且這個屬性如果不能被自動裝配将被忽略。換句話說,

@Required

更強大,因為它強制通過容器支援的任何方式設定屬性,如果沒有定義值,則會引發相應的異常。

另外,你可以通過Java 8的

java.util.Optional

來表達特定依賴項的非必需性質,類似以下示例所示:

public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(Optional<MovieFinder> movieFinder) {
        ...
    }
}           

從Spring5.0後,你可以使用

@Nullable

注解(任何包裝中的任何種類-例如,JSR-305的

javax.annotation.Nullable

)或者隻是利用

Kotlin

内置的

null

安全支援:

public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(@Nullable MovieFinder movieFinder) {
        ...
    }
}           

你可以為這些接口使用

@Autowired

,這是衆所周知的可注入的依賴:

BeanFactory

ApplicationContext

Environment

ResourceLoader

ApplicationEventPublisher

, 和

MessageSource

。這些接口和它們的拓展接口,例如,

ConfigurableApplicationContext

ResourcePatternResolver

是自動地解析,沒有特殊的安裝需要。下面的例子主動注入一個

ApplicationContext

對象:

public class MovieRecommender {

    @Autowired
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}           

@Autowired

@Inject

@Value

@Resource

注解是通過Spring的

BeanPostProcessor

實作處理的。這意味着你不能應用這些注解在你自己的

BeanPostProcessor

BeanFactoryPostProcessor

類中。這些類型必須被通過XML或者Spring

@Bean

方法連接配接。

參考代碼:

com.liyong.ioccontainer.starter.XmlIocAnnotationContainerConfigrationntainer

1.9.3 使用

@Primary

對基于注解的自動裝配調整

因為通過類型的自動裝配可能導緻多個候選者,通常有必要需要更多的選擇處理。一種可以完成的方式是使用Spring的

@Primary

@Primary

表示當多個bean自動裝配到單值依賴時這個bean是我們更期望的值。如果主要的bean在限定符候選bean中時,它将被自動裝配。

考慮下面的配置,這個配置定義

firstMovieCatalog

作為主要的

MovieCatalog

類型候選值:

@Configuration
public class MovieConfiguration {

    @Bean
    @Primary
    public MovieCatalog firstMovieCatalog() { ... }

    @Bean
    public MovieCatalog secondMovieCatalog() { ... }

    // ...
}           

通過前面的配置,下面的

MovieRecommender

類中自動裝配

firstMovieCatalog

public class MovieRecommender {

    @Autowired
    private MovieCatalog movieCatalog;

    // ...
}           

對應bean的定義如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>
     <!--作為主要的bean-->
    <bean class="example.SimpleMovieCatalog" primary="true">
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>           

1.9.4 使用Qualifiers對基于注解的自動裝配調整

@Primary

是一種根據類型使用自動裝配的有效方法,當可以确定一個主要候選對象時,可以使用多個執行個體。當你需要更多的控制選擇處理時,你可以使用Spring的

@Qualifier

注解。你可以将限定符值與特定的參數相關聯,進而縮小類型比對的範圍,以便每個參數選擇特定的bean。在這個簡單的例子中,這可以是簡單的描述性值,如以下示例所示:

public class MovieRecommender {

    @Autowired
    @Qualifier("main")
    private MovieCatalog movieCatalog;

    // ...
}           

你也可以指定

@Qualifier

注解在各個構造函數參數或方法參數上,類似下面的例子:

public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(@Qualifier("main") MovieCatalog movieCatalog,
            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...
}           

下面的例子顯示對應的bean定義:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier value="main"/> //1

        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <qualifier value="action"/> //2

        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>           
  1. 具有

    main

    限定符值的Bean與構造函數參數連接配接,構造函數參數使用相同的值進行限定
  2. 帶有

    action

    限定符值的bean與構造函數參數連接配接,構造函數參數使用相同的值進行限定。

為了回退比對,bean的名字是預設的限定符。是以,你可以定義bean的id為main替代嵌入的限定符元素,導緻相同的比對結果。然而,即使你能使用這個約定通過名字指定bean,

@Autowired

基本上是關于帶有可選語義限定符的類型驅動的注入(意思是

@Autowired

中帶有require限定符)。這意味着限定符值總是有縮小類型比對集合的語義,即使bean名為回退名稱。他們從語義上不能表達引用一個唯一bean的id。好的限定符值是

main

EMEA

persistent

,它們表示獨立于bean

id

的特定元件的特征,對于匿名bean定義(如前面示例中的定義),可以自動生成這些特征。

限定符也能應用到類型化的集合,像前面讨論的-例如,Set。在這種例子中,所有比對的bean,根據聲明的限定符,作為一個集合被注入。這暗示限定符沒有必要是唯一的。相反,它們構成了過濾标準。例如,你可以定義具有相同限定符值

action

的多個MovieCatalog Bean,所有這些都注入到以

@Qualifier(“ action”)

注解的Set 中。

在類型比對的候選者中,讓限定符值選擇目标bean名稱,在注入點不需要@Qualifier注解。如果沒有其他解析訓示器(例如限定符或primary标記),對于非唯一依賴情況,Spring将注入點名(即字段名或參數名)與目标bean名相比對,并選擇同名的候選項(如果有的話)。

也就是說,如果你打算試圖通過名稱表示注解驅動注入,不要主要地使用@Autowired,即使它有通過名稱在類型比對的候選者間選擇的能力。相反,使用JSR-250

@Resource

注解,它是語義地通過使用唯一名稱定義辨別一個指定目标元件,聲明的類型與比對過程無關。@Autowired有不同的語義:按類型選擇候選bean之後,指定的字元串限定符值僅在那些類型選擇的候選中被考慮(例如,比對一個

account

限定符與bean被标記相同的限定符标簽)。

對于定義為集合、Map、數組類型的bean,

@Resource

是一個好的解決方案,通過唯一的名稱引用指定的集合或數組bean。也就是說,在Spring4.3以後你可以比對一個Map和數組類型通過Spring的

@Autowired

類型比對算法,隻要元素類型資訊保留在@Bean傳回類型簽名或集合繼承層次結構中。在這種場景中,你可以使用限定符值去相同類型集合選擇,如前一段所述。

在Spring4.3以後,

@Autowired

還考慮了注入的自我引用(也就是說,引用回目前注入的bean)。注意自我注入是一種後備。對其他元件的正常依賴始終優先。從這個意義上說,自我推薦不參與正常的候選人選擇,是以尤其是絕不是主要的。相反,它們總是以最低優先級結束。實際上,你應該僅将自我引用用作最後的手段(例如,在相同執行個體上通過bean的事物代理調用其他方法)。在這種情況下,考慮将受影響的方法分解為單獨的委托Bean。或者,你可以使用

@Resource

,它可以通過其唯一名稱擷取傳回到目前bean的代理。

嘗試将

@Bean

方法的結果注入相同的配置類也是一種有效的自引用方案。要麼在實際需要的方法簽名中懶惰地解析此類引用(與配置類中的自動裝配字段相對),要麼将受影響的

@Bean

方法聲明為靜态,将其與包含的配置類執行個體及其生命周期脫鈎。否則,僅在回退階段考慮此類Bean,而将其他配置類上的比對Bean選作主要候選對象(如果可用)。

@Autowired

應用到字段、構造函數和多參數方法,在參數級别允許使用限定符注解縮小選擇範圍。相反,

@Resource

僅僅支援字段和bean屬性Setter方法簡單參數。是以,如果注入目标是構造函數或多參數方法,則應堅持使用限定符。

你可以建立你自己的自定義限定符注解。為了這樣做,定義一個注解和提供

@Qualifier

注解在你的定義中,類似下面例子:

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

    String value();
}           

然後,你可以提供自定義限定符在自動裝配字段和參數上,類似下面例子:

public class MovieRecommender {

    @Autowired
    @Genre("Action")
    private MovieCatalog actionCatalog;

    private MovieCatalog comedyCatalog;

    @Autowired
    public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
        this.comedyCatalog = comedyCatalog;
    }

    // ...
}           

接下來,你可以為候選者bean定義提供資訊。你可以增加标簽的子标簽并且指定

type

value

為比對你的自定義限定符注解。這個類型是注解的全限定類名的比對。或者,為友善起見,如果不存在名稱沖突的風險,則可以使用簡短的類名。下面例子展示兩種方式:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
      <!--類名簡寫-->
        <qualifier type="Genre" value="Action"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
      <!--全限定類名-->
        <qualifier type="example.Genre" value="Comedy"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>           

Classpath掃描群組件管理中

,你可以看到在XML中基于注解另類的提供限定符中繼資料,檢視

Providing Qualifier Metadata with Annotations

在某些場景中,使用注解不需要值可能就滿足。這是非常有用的,當注解應用更一般的用途和可以應用于幾種不同類型的依賴項時。例如,你可以提供一個脫機目錄,當沒有Internet連接配接可用時可以進行搜尋。首先,定義相同的注解,類似下面的例子:

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

}           

然後,增加注解到字段或者屬性去自動裝配,類似下面例子:

public class MovieRecommender {

    @Autowired
    @Offline //1
    private MovieCatalog offlineCatalog;

    // ...
}           
  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,并解析我們熟知的可解析依賴項:

BeanFactory

ApplicationContext

ResourceLoader

ApplicationEventPublisher

MessageSource

接口。

是以,在下面的例子中,

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初始化失敗。它也可以使用方法類似

setPlaceholderPrefix

setPlaceholderSuffix

setValueSeparator

去自定義占位符。

Spring Boot 通過預設的

PropertySourcesPlaceholderConfigurer

bean配置,它能從

application.properties

application.yml

檔案擷取屬性。

Spring提供的内置轉換器支援允許自動處理簡單的類型轉換(例如,轉換為Integer或int)。多個逗号分隔的值能夠自動的轉換為String數組不需要額外的操作。

它也可能提供一個預設值類似下面:

@Component
public class MovieRecommender {

    private final String catalog;
    //defaultCatalog為預設值
    public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
        this.catalog = catalog;
    }
}           

Spring

BeanPostProcessor

在使用

ConversionService

處理将

@Value

中的String值轉換為目标類型的過程。如果你想提供轉換支援為你自己的自定義類型,你可以提供你自己的

ConversionService

bean執行個體類似下面例子:

@Configuration
public class AppConfig {
    @Bean
    public ConversionService conversionService() {
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
        conversionService.addConverter(new MyCustomConverter());
        return conversionService;
    }
}           

@Value

包含

SpEl

表達式時候,這個值将被在運作時動态地計算,類似下面例子:

@Component
public class MovieRecommender {

    private final String catalog;

    public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
        this.catalog = catalog;
    }
}           

SpEL

還可以使用更複雜的資料結構:

@Component
public class MovieRecommender {

    private final Map<String, Integer> countOfMoviesPerCatalog;

    public MovieRecommender(
            @Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
        this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
    }
}           

com.liyong.ioccontainer.starter.XmlValueIocContainer

1.9.9 使用

@PostConstruct

@PreDestroy

CommonAnnotationBeanPostProcessor

不僅僅識别

@Resource

注解也能識别JSR-250生命周期注解:

javax.annotation.PostConstruct

javax.annotation.PreDestroy

。在Spring 2.5中引入的對這些注解的支援為初始化回調和銷毀回調中描述的生命周期回調機制提供了一種替代方法。

CommonAnnotationBeanPostProcessor

在Spring的

ApplicationContext

中被注入,在生命周期的同一點上(備注:同一類回調方法,比如:銷毀方法),與相應的Spring生命周期接口方法或顯式聲明的回調方法調用帶有其中一個注解的方法。在下面的例子中,在以下示例中,緩存在初始化時預先填充,并在銷毀時清除。

public class CachingMovieLister {

    @PostConstruct
    public void populateMovieCache() {
        // 填充緩存
    }

    @PreDestroy
    public void clearMovieCache() {
        // 清除緩存
    }
}           

有關組合各種生命周期機制的詳細資訊,檢視

組合生命周期機制
類似

@Resource

一樣,

@PostConstruct

@PreDestroy

注解類型是從JDK6到JDK8的标準Java庫的一部分。然而,整個

javax.annotation

包與JDK 9中的核心Java子產品分離,最終在JDK 11中删除。如果需要,

javax.annotation-api

可以通過Maven中央庫擷取,簡單地增加到應用的類路徑下面和其他庫一樣。

com.liyong.ioccontainer.starter.XmlLifecycleIocContainer

作者

個人從事金融行業,就職過易極付、思建科技、某網約車平台等重慶一流技術團隊,目前就職于某銀行負責統一支付系統建設。自身對金融行業有強烈的愛好。同時也實踐大資料、資料存儲、自動化內建和部署、分布式微服務、響應式程式設計、人工智能等領域。同時也熱衷于技術分享創立公衆号和部落格站點對知識體系進行分享。

部落格位址:

http://youngitman.tech

CSDN:

https://blog.csdn.net/liyong1028826685

微信公衆号:

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

技術交流群:

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