日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

Spring —— IoC 容器详解

發布時間:2025/3/12 javascript 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Spring —— IoC 容器详解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

引言

本篇博客總結自官網的《The IoC Container》,其中會結合王富強老師的《Spring揭秘》融入自己的語言和理解,爭取通過這一篇文章徹底掃除spring IOC的盲區。

本文介紹什么是 IoC 容器,什么是 Bean,依賴,Bean Definition,Bean Factory 等概念知識。

文中會大量出現 xml 的容器配置文件,雖然由于目前 Spring Boot 項目的流行,人們已經很少使用 xml 配置,而且更喜好 Java Config 的配置,但配置只是形式的不同,其內部邏輯都是共通的!

一、什么是 Spring IoC 容器?什么是 Bean?

IoC 也可以理解為依賴注入(dependency injection)(參考《控制反轉 IOC 與依賴注入 DI》),它是一個只通過構造函數參數、工廠方法參數、或通過屬性賦值等方式去定義它們的依賴的一個過程,簡言之,就是屬性對象賦值。

【《Spring揭秘》:在Spring中,存在三種依賴注入的方式接口注入、構造器注入、setter注入

1、接口注入:要求實現某個接口才能完成依賴注入,本身帶有很強的侵入性,目前已經“退役”

2、構造方法注入:優點是對象在構造完成之后,即已進入就緒狀態,可以馬上使用。缺點是,當依賴對象比較多的時候,構造方法的參數列表會比較長。而通過反射構造對象的時候,對相同類型的參數的處理會比較困難,維護和使用上也比較麻煩。而且在Java中,構造方法無法被繼承,無法設置默認值。

3、setter方法注入:setter方法描述性更強,可以被繼承,允許設置默認值。缺點是對象無法在構造完成后馬上進入就緒狀態。

用一句話概括IOC可以給我們帶來什么?IOC是一種可以幫助我們解耦各業務對象間依賴關系的對象綁定方式。——《Spring解密》

IoC容器會在創建對象的時候注入這些依賴,這基本上是 bean 自己去控制實例化的一個反向,因此叫 Inversion of control 控制反轉。

org.springframework.beans 和 org.springframework.context 包是 Spring IoC 容器的基礎。BeanFactory 接口提供了一種高級的配置機制,使得 Spring 有能力去管理對象的類型。

ApplicationContext 是 BeanFactory 的一個子接口,它添加了一些額外的特性:

1、更簡單的 Spring AOP 特性的集成

2、信息資源處理(用于國際化)

3、事件發布

4、應用程序層面的特定上下文,例如 WebApplicationContext 之于 Web 項目。

簡而言之,BeanFactory 提供了配置框架和基本功能,ApplicationContext 加入更適合企業級特性的功能。

【《Spring揭秘》:

兩種容器類型的基本區別:

1、BeanFactory:基礎類型IOC容器。默認采用延遲初始化策略(lazy-load)(受管對象直到客戶端請求時才進行初始化及依賴注入)。容器啟動速度較快,所需資源有限。

2、ApplicationContext:繼承自BeanFactory,擁有BeanFactory的所有支持,并擴展了高級特性。該類型容器啟動后,默認對受管對象全部初始化并綁定完成容器啟動速度較慢,所需資源更多。

對容器的比喻(如何看待bean工廠的存在?):

BeanFactory就像一個汽車生產廠。你將汽車零件送入這個汽車生產廠,最后,只需要從生產線的終點取得成品汽車就可以了。】

在 Spring 中,組成應用主干并且由 Spring IoC 容器管理的對象被稱為 Bean。bean 是一個由 spring IoC 容器實例化、裝配和管理的對象。否則,bean 就是一個應用程序中許許多多對象中的一個普通一員。Bean 和它的依賴都通過IoC容器使用的配置文件來描述。

org.springframework.context.ApplicationContext 接口就代表 Spring 的 IoC 容器,它的職責就是 實例化、配置、裝配各種 bean。容器通過讀取配置文件獲得實例化、配置、裝配什么對象的指令。配置信息可以使用 xml、Java 注解、或者 Java 代碼來描述。

Spring 已經提供了一些 ApplicationContext 接口的實現。在單體應用中,通常會創建 ClassPathXmlApplicationContext 或 FileSystemXmlApplicationContext 的實例對象。

在更多數的應用場景中,不需要顯式的用戶代碼來實例化一個或多個 Spring IoC 容器。下面的圖解展示了 Spring 是如何工作的:

二、用于描述 IoC 容器的配置信息

如前面圖解所示,spring IoC 容器需要使用一些配置信息。這些配置信息表示你將要告訴 IoC 容器如何去實例化、配置、裝配應用程序中的對象。

配置信息通常以簡單直觀的XML格式提供,本文也會以這種配置形式來講解關鍵概念和 IoC 容器的特性。

提示:xml 并不是唯一的配置形式。不同的配置形式與 Spring 框架本身完全解耦,目前,很多開發者更偏好使用基于Java? Config 的配置形式。?

1、基于注解(如@Service、@Component等)的配置,是 Spring 2.5 引入的支持以注解形式描述配置信息的一種方式。

2、基于Java 的配置,從 Spring 3.0 開始,JavaConfig 提供的許多特性已經變成 Spring Framework 的核心。這樣,你可以通過 Java 而不是 xml 文件來定義應用程序中的各種類。例如 @Configuration、@Bean、@Import 和 @DependsOn 等。

Spring 容器的配置由至少一個 bean 組成。xml 形式的配置以<bean/> 標簽來描述這些 bean 定義,并以頂級標簽<beans/> 來包裹他們。JavaConfig 形式的配置則需要在一個標記了@Configuration 的類中的 bean定義的方法上標記@Bean。

這些 bean 定義直接對應著組成你的應用程序的真正的對象。最典型的,就是定義 Service 層對象、DAO對象(data access objects),基礎設施對象如Hibernate 的 SessionFactories,JMS 的 Queues 等等。通常,并不需要配置細粒度的域對象(domain objects 即實體類對象),因為創建和加載它們通常是 DAO 和業務邏輯的職責。

下面的例子展示了 XML 結構的配置(id 屬性是?bean 的唯一標識,class 屬性以全類名定義 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"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="..." class="..."> <!-- 內部配置協作者和配置信息 --></bean><bean id="..." class="..."><!-- 內部配置協作者和配置信息 --></bean><!-- 更多的bean配置 --> </beans>

三、容器的應用

3.1 實例化一個容器

ApplicationContext 構造函數允許傳入配置信息的位置路徑,可以是系統路徑,也可以是項目的 classpath 下。

ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

提示:Spring Resource 抽象提供了一種更便捷的機制,允許從一個 URI 定位信息中以 InputStream 來讀取資源。更多描述參考:Application Contexts and Resource Paths

services.xml 的配置信息示例如下:

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><!-- services --><bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl"><property name="accountDao" ref="accountDao"/><property name="itemDao" ref="itemDao"/><!-- additional collaborators and configuration for this bean go here --></bean><!-- more bean definitions for services go here --></beans>

daos.xml 的配置信息示例如下:

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="accountDao"class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao"><!-- additional collaborators and configuration for this bean go here --></bean><bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao"><!-- additional collaborators and configuration for this bean go here --></bean><!-- more bean definitions for data access objects go here --></beans>

bean 的定義可以跨越多個配置文件。如上所示,services.xml 中定義的 “petStore” 依賴了 daos.xml 中定義的 “accountDao ”和 “itemDao”。通常,每個單獨的 XML? 配置文件都代表一個邏輯層或者應用架構中的一個模塊

可以使用應用程序上下文構造器來加載所有這些 xml 片段中的 bean 定義。這些構造器可接收多個 Resource 位置,如:

ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

或者,使用 <import/> 來加載其他文件中的 bean 定義。例如:

<beans><import resource="services.xml"/><import resource="resources/messageSource.xml"/><import resource="/resources/themeSource.xml"/><bean id="bean1" class="..."/><bean id="bean2" class="..."/> </beans>

上例中,外部的 bean 定義會從三個文件中加載過來:services.xml、messageSource.xml、themeSource.xml。所有的位置路徑都是執行導入操作文件(以下簡稱“執行 import 文件”)的相對路徑,所以 services.xml 必須和執行 import 文件在相同的文件夾,messageSource.xml 和 themeSource.xml 必須在執行 import 文件所在路徑下的 resources 文件夾中。?如你所見,開頭的斜杠可以忽略。但是給定的路徑一定是相對路徑,所以最好的形式就是使用不帶斜杠的形式。包括頂層<beans>標簽一起都會導入進來,要求被導入的一定要是有效的 xml bean 定義文件。

提示:如果想引用一個父文件夾中的文件,使用 “../” 的形式是可以的,但并不推薦。因為這么做會創建一個相對于當前應用程序之外的文件依賴。尤其不推薦引用路徑相對于 classpath: URL(例如:classpath:../services.xml),這樣會讓運行時解析程序既要選擇“最近的” classpath 根路徑,還要去它的父路徑中檢查。類路徑配置更改可能導致選擇不同的、不正確的目錄。

你也可以使用絕對路徑,例如,file:C:/config/services.xml 或者 classpath:/config/services.xml。但是,你要知道這么做會讓你的應用程序配置和特殊的絕對路徑耦合

3.2 使用 IoC 容器(updated on 2020-10-14)

【《Spring揭秘》BeanFactory的對象注冊與依賴綁定方式:

相關接口:

BeanFactory接口定義了最基本的獲取bean的方法,包括各種方式的getBean,以及對bean的一些判斷,包括isSingletoncontainsBean

BeanDefinitionRegistry接口定義了基本的管理bean的方法,包括注冊、移除等,從名字可以看出,這個接口針對的都是BeanDefinition類型,這也是bean在容器中的信息模板對象。

DefaultListableBeanFactory類是默認的通用bean工廠,(間接)實現了前面兩個接口,具有了獲取bean和注冊bean的能力。

如何理解BeanFactory和BeanDefinitionRegistry的關系?

打個比方,BeanDefinitionRegistry就像圖書館的書架,所有的書都放在書架上,借書還書都是跟圖書館(BeanFactory)打交道,但書架才是圖書館存放書籍的地方,即:BeanDefinitionRegistry就是BeanFactory的書架

(BeanFactory只是一個接口,我們最終需要一個該接口的實現來進行實際的bean管理,DefaultListableBeanFactory就是這樣一個比較通用的BeanFactory實現類。它除了間接實現了BeanFactory接口,還實現了BeanDefinitionRegistry接口,該接口才是在BeanFactory的實現中擔當bean注冊管理的角色。

基本上,BeanFactory接口只定義如何訪問容器內管理的bean的方法,各個BeanFactory的具體實現類負責具體bean的注冊以及管理工作。BeanDefinitionRegistry接口定義抽象了bean的注冊邏輯。通常情況下,具體的BeanFactory實現類會實現這個接口來管理bean的注冊)】

ApplicationContext 是一個接口,是一個更高級的對象工廠(advanced factory),可以維護各種 bean 和它們的依賴。使用 getBean(String name, Class<T> requiredType)方法,你可以拿到你已經定義好的 bean。ApplicationContext 允許你讀取 bean 定義和訪問他們。例如:

// create and configure beans ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");// retrieve configured instance PetStoreService service = context.getBean("petStore", PetStoreService.class);// use configured instance List<String> userList = service.getUsernameList();

你可以使用 getBean 來取得 bean 的實例。ApplicationContext 接口有一些其他方法來取得 bean ,但是,理想狀態下,你的應用程序代碼永遠也不應該使用這些方法。是的,你的應用程序根本不應該使用 getBean() 方法,這樣就不會耦合 Spring API。例如,Spring 集成了 web 框架,提供了各種 web 框架組件的依賴注入,例如 controller 和 JSF-managed bean,允許你通過元數據(如自動注入注解 @Autowired)來聲明一個特殊的bean。

四、Bean 概述

4.1 什么是 Bean Definition?(updated on 2020-10-14)

一個 IoC 容器管理一個或多個 bean。這些 bean 由你提供給容器的配置信息創建(例如,在 xml 中由 <bean/> 定義)。

在容器內部,這些 bean 的定義被描述為 BeanDefinition 實例(RootBeanDefinition和ChildBeanDefinition是BeanDefinition的兩個主要實現類,包含以下這些元數據(部分):

1、一個全類名。通常是定義的 bean 的實際實現類

2、Bean 的行為配置元素,聲明bean在容器中的狀態行為(作用域、生命周期回調,等等)。

3、對bean執行其工作所需的其他bean的引用。這些引用也稱為協作者或依賴項

4、其他配置,例如,池的大小限制或在管理連接池的bean中使用的連接數。

這些元數據被轉換為一系列的組成 BeanDefinition 的屬性。下表描述了這些屬性信息:

屬性名詳細描述鏈接

Class

Instantiating Beans

Name

Naming Beans

Scope

Bean Scopes

Constructor arguments

Dependency Injection

Properties

Dependency Injection

Autowiring mode

Autowiring Collaborators

Lazy initialization mode

Lazy-initialized Beans

Initialization method

Initialization Callbacks

Destruction method

Destruction Callbacks

除了 bean definition 這種包含了如何創建一個具體對象的 bean 定義之外,ApplicationContext 的實現也允許容器之外的已存在的對象注冊進來。只需要使用 ApplicationContext 的 BeanFactory——getBeanFactory() 方法。這個方法會返回 DefaultListableBeanFactory 對象,它是 BeanFactory的實現類。DefaultListableBeanFactory 中的 registerSingleton(..) 和 registerBeanDefinition(..) 方法支持這種容器外注冊操作。然而,典型的應用程序僅使用通過常規bean定義元數據定義的bean。

提示:bean 的元數據和手動提供的單例實例需要盡早注冊,這是為了容器能夠在自動裝配和其他自省步驟中正確地對它們進行推理。雖然在某種程度上支持覆蓋存在的元數據和存在的單例對象,但在運行時(并發地實時訪問工廠)注冊新的 bean不受官方支持,并可能導致并發訪問異常,或容器中 bean 的狀態不一致,或者兩者都有可能。

4.2 Bean 的命名(updated on 2020-10-14)

每個 bean 都有一個或多個標識(identifiers)。這些標識在 bean 定義的所屬容器中必須是唯一的。一個 bean 通常只有一個 標識,但是,如果它需要更多的標識,其他的可視為別名。

在 xml 配置中,你可以使用 id 屬性 和 name 屬性,或者同時使用。id 屬性代表唯一 id 。按照慣例,這些名稱是字母數字('myBean'、'someService'等),但它們也可以包含特殊字符。如果你想為這些 bean 引入其他別名,也可以指定 name 屬性,以逗號、分號 或空白字符間隔即可。但不建議指定過多的別名,這會造成一定的兼容問題。

其實你并不需要為 bean 指定 name 或 id ,容器會自動為這些 bean 生成一個唯一名稱。但是如果你想通過名稱來引用另一個 bean,可以使用 ref 或 Service Locator 風格查找,就必須為 bean 指定一個名稱。不提供名稱往往和使用內部 bean(inner bean) 和自動裝配協作者(autowiring collaborators)有關。

Bean 的命名約定

bean 命名約定遵循標準的 Java 對實例屬性名稱的命名慣例。也就是,bean 的名稱以小寫字母開頭配合駝峰命名法。例如:accountManager、accountService、userDao、loginController 等等。

一致的 bean 命名風格可以讓你的配置更容易閱讀和理解。同樣,如果你使用 Spring?AOP,這樣的命名在將通知應用到一組名稱相關的 bean 時很有幫助。

提示:通過類路徑(classpath)掃描,Spring 會為未命名的組件生成一個名字。遵從的規則就是前面的規則 :取得類名,然后把首字母變成小寫。但是如果一些特殊情況,比如,有一個以上的字符,且第一個和第二個字符都是大寫,那么會保留原始大小寫。這個規則和 java.beans.Introspector.decapitalize 的規則一致。

Java Config 配置 Bean 的名稱

?如果使用 Java Config 配置 bean,可以使用 @Bean 注解的 name 屬性,支持傳入多個別名:

public @interface Bean {@AliasFor("name")String[] value() default {};@AliasFor("value")String[] name() default {};// initMethod()、destroyMethod() ... }

如果只有一個名稱,可以像這樣配置:@Bean("accountService")。

4.3 Bean 的實例化(updated on 2020-10-14)

一個 bean 定義本質上就是一個創建一個或多個對象的“配方”(recipe,或食譜)。當被請求時,容器查看已命名bean的“配方”,并使用該 bean 定義封裝的配置元數據來創建(或獲取)實際對象。

<bean>標簽的 class 屬性表示該 bean 的類型,這個屬性是必須的。在 BeanDefinition 內部,就是 Class 屬性。你可以使用下面兩種方式其一來使用 Class 屬性。

1、大多數情況,容器通過反射調用對象的構造器來創建 bean ,class 用于指定一個構造類型,有點類似于 new 操作。

2、指定一個包含靜態工廠方法的類,這個靜態工廠方法會創建對象,這中情況不多見。從靜態工廠方法返回的對象類型,可以是本類,也可以完全是另一個類。

內部類名稱

如果你想為一個靜態內部類(static nested class)配置一個 bean,那必須使用該內部類的二進制名稱(the binary name of the nested class)?。

例如,如果你有一個類叫做 SomeThing ,包名是 com.example,而且這個 SomeThing 類有一個靜態內部類叫做 OtherThing,那么 bean 定義的 class 屬性就應該是:com.example.SomeThing$OtherThing。

注意 $ 符號的使用,它將內部類的名稱與外部類的名稱分隔開。

4.3.1 使用構造器實例化 bean

Spring 極大的兼容了這種創建 bean 的方式,也就是說,被實例化的類不需要實現任何特定的接口或以特殊的方式進行編碼。簡單地指定聲明 bean 的類型就足夠。但是,視創建指定bean 的?IOC 容器的類型而定,你可能會需要一個默認 bean 構造器。

Spring IoC容器實際上可以管理你希望它管理的任何類。而不限于 JavaBean。絕大多數 Spring 用戶更喜歡實際的 JavaBean,它只有一個默認的(無參數的)構造器和適當的setter和getter方法,這些方法是根據容器中的屬性建模的。你還可以在容器中使用更多奇異的非bean樣式的類。例如,你需要使用絕對不符合JavaBean規范的遺留的連接池,Spring也可以管理它。

以 xml 的形式,你可以像下面這樣:

<bean id="exampleBean" class="examples.ExampleBean"/> <bean name="anotherExample" class="examples.ExampleBeanTwo"/>

更多關于如何向構造器提供參數(如果需要的話)和對象構造結束后為對象賦值的機制,參考:Injecting Dependencies。?

4.3.2 使用靜態工廠方法實例化 bean

當定義一個使用靜態工廠方法創建的 bean 時,使用 class 屬性指定包含了靜態工廠方法的類,然后再使用 factory-method 屬性來指定工廠方法的名字。如下所示:

<bean id="clientService"class="examples.ClientService"factory-method="createInstance"/>

而這個靜態方法應該是真實可被調用的:

public class ClientService {private static ClientService clientService = new ClientService();private ClientService() {}public static ClientService createInstance() {return clientService;} }

更多有關向工廠方法提供參數和返回對象后如何賦值的機制,參考:?Dependencies and Configuration in Detail。?

4.3.3?使用實例工廠方法實例化 bean

還有一種方法是通過實例工廠方法創建 bean。和靜態工廠方法類似,使用實例工廠方法進行實例化會調用一個“非靜態”的已存在容器內的bean的方法來創建對象。

使用這種機制時,class 屬性為空即可,factory-bean 屬性指定一個在當前(或父或祖先)容器內的 bean 名稱,這個 bean 需要包含一個可以被用于創建對象的工廠方法。剩下的,就和靜態工廠的配置差不多了:

<!-- the factory bean, which contains a method called createInstance() --> <bean id="serviceLocator" class="examples.DefaultServiceLocator"><!-- inject any dependencies required by this locator bean --> </bean><!-- the bean to be created via the factory bean --> <bean id="clientService"factory-bean="serviceLocator"factory-method="createClientServiceInstance"/> public class DefaultServiceLocator {private static ClientService clientService = new ClientServiceImpl();public ClientService createClientServiceInstance() {return clientService;} }

另外,一個工廠類可以被多個工廠方法依賴:

<bean id="serviceLocator" class="examples.DefaultServiceLocator"><!-- inject any dependencies required by this locator bean --> </bean><bean id="clientService"factory-bean="serviceLocator"factory-method="createClientServiceInstance"/><bean id="accountService"factory-bean="serviceLocator"factory-method="createAccountServiceInstance"/>

對應的類:

public class DefaultServiceLocator {private static ClientService clientService = new ClientServiceImpl();private static AccountService accountService = new AccountServiceImpl();public ClientService createClientServiceInstance() {return clientService;}public AccountService createAccountServiceInstance() {return accountService;} }

?在 Spring 文檔中,“factory bean” 指的是在 Spring 容器中配置的可以通過實例工廠或靜態工廠方法創建對象的一種 bean。相對的,FactoryBean(注意大小寫)指的是 Spring 框架特有的 FactoryBean 接口及實現類。

?【《Spring揭秘》關于FactoryBean:

當某些對象的實例化過程過于繁瑣,通過xml配置過于復雜,就可以實現FactoryBean接口,給出對象實例化邏輯代碼。FactoryBean是Spring提供的對付這種情況的“制式裝備”(正規軍)。

FactoryBean的三個抽象方法

1、getObject()方法會返回該FactoryBean“生產”的對象實例,我們需要實現這個方法以給出自己的對象實例化邏輯。

2、getObjectType()方法返回對象的類型,如果預先無法確定,則返回null。

3、isSingleton()方法用于表明getObject()返回的對象是否以singleton形式存在。

案例:

public class NextDayDateFactoryBean implements FactoryBean{ public Object getObject() throws Exception{return new DateTime().plusDays(1); } public Class getObjectType(){ return DateTime.class; }public boolean isSingleton(){ return false; } } <bean id=”displayer” class=”...Displayer”><property name=”nextDay”><ref bean=”nextDayDate”></property> </bean> <bean id=”nextDayDate” class=”...NextDayDateFactoryBean”> </bean> public class Displayer {private DateTime nextDay;// ... }

提示:FactoryBean類型的bean定義,通過正常的 id 引用,容器返回的是FactoryBean所“生產”的對象類型,而非FactoryBean類型本身】

4.4 判斷 Bean 的運行時類型(updated on 2020-10-14)

具體 bean 的運行時類型并不容易確定。bean 定義中指定的 class 只不過是一個初始類型參考,可能潛在綁定了一個聲明的工廠方法,或者作為一個可能會導致一個不同運行時 bean 類型的?FactoryBean,或在實例工廠方法中根本不設置(如前所述)。此外,AOP的代理可能會使用基于接口的代理對象包裹 bean 實例,這也會限制目標 bean 的真實類型的暴露。

找出指定 bean 的真實運行時類型的方法,推薦使用 BeanFactory.getType 方法,使用 bean 的名稱作為參數。上述所有情況都包括在內,這個方法可以返回相同 bean 名稱的對象的類型。

五、依賴

5.1 Dependency Injection 依賴注入

依賴注入是一個只通過構造函數參數、工廠方法參數、或通過屬性賦值等方式去定義它們的依賴的一個過程,簡言之,就是屬性賦值。

容器會在創建 bean 的時候注入那些它需要的依賴。這基本是由對象自己控制實例化的反向操作,因此得名控制反轉。

使用 DI 原則會讓代碼更加整潔,而且,當對象被它們的依賴所提供時,也可以更好的解耦。對象不再需要去查找它的依賴,也不需要知道這些依賴的位置和類型信息。因此,你的類就會更容易的去做測試。

DI 存在兩種主要的變體:構造器依賴注入Setter 依賴注入

構造器依賴注入通過容器調用一定數量參數的構造來完成,每一個參數都代表一個依賴項。調用靜態工廠方法,傳入指定參數來構造 Bean 的形式和這差不多。下面代碼展示了只能用構造器注入的依賴注入模式:

public class SimpleMovieLister {// the SimpleMovieLister has a dependency on a MovieFinderprivate MovieFinder movieFinder;// a constructor so that the Spring container can inject a MovieFinderpublic SimpleMovieLister(MovieFinder movieFinder) {this.movieFinder = movieFinder;}// business logic that actually uses the injected MovieFinder is omitted... }

注意,上述代碼并沒有任何特別之處。這就是一個 POJO ,沒有依賴容器的任何特定接口、基礎類或注解。

使用參數類型就會觸發構造器參數類型解析匹配。如果bean定義的構造函數參數中不存在潛在的歧義,那么構造函數參數在bean定義中定義的順序就是實例化bean時將這些參數提供給適當的構造函數的順序。考慮以下類:

public class ThingOne {public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {// ...} }

假設 ThingTwo 和 ThingThree 類沒有繼承關系,沒有潛在的歧義存在。這樣,下面的配置就可以很好的工作,你不需要在<constructor-args/>標簽中顯式地指定構造器參數下標或類型。

<beans><bean id="beanOne" class="x.y.ThingOne"><constructor-arg ref="beanTwo"/><constructor-arg ref="beanThree"/></bean><bean id="beanTwo" class="x.y.ThingTwo"/><bean id="beanThree" class="x.y.ThingThree"/> </beans>

當另一個 bean 被引入,且類型已知,那么就會匹配到。當用到一個簡單類型,如<value>true</value>,Spring 無法判斷值的類型,也因此無法自主匹配。如下所示:

public class ExampleBean {// Number of years to calculate the Ultimate Answerprivate int years;// The Answer to Life, the Universe, and Everythingprivate String ultimateAnswer;public ExampleBean(int years, String ultimateAnswer) {this.years = years;this.ultimateAnswer = ultimateAnswer;} }

這種情況,如果希望容器成功完成類型匹配,就需要我們顯式指定類型信息,使用 type 屬性即可。這適用于使用簡單類型作為構造器參數的情況,如下所示:

<bean id="exampleBean" class="examples.ExampleBean"><constructor-arg type="int" value="7500000"/><constructor-arg type="java.lang.String" value="42"/> </bean>

同時也可以使用 index 屬性來顯式指定構造器參數的下標,注意下標從 0 開始。如下所示:

<bean id="exampleBean" class="examples.ExampleBean"><constructor-arg index="0" value="7500000"/><constructor-arg index="1" value="42"/> </bean>

下標有時還可解決構造器具有相同簡單類型時出現的歧義問題。

也可以使用參數名稱來消除歧義:

<bean id="exampleBean" class="examples.ExampleBean"><constructor-arg name="years" value="7500000"/><constructor-arg name="ultimateAnswer" value="42"/> </bean>

注意,這種方式需要配合 debug 標志,以便 Spring 可以找到構造器的參數名,你可以使用 @ConstructorProperties JDK注解來顯式地命名你的構造器參數,如下所示:

public class ExampleBean {@ConstructorProperties({"years", "ultimateAnswer"})public ExampleBean(int years, String ultimateAnswer) {this.years = years;this.ultimateAnswer = ultimateAnswer;} }

Setter 依賴注入,通過容器調用無參構造或無參靜態工廠方法實例化 bean 之后調用 setter 方法來完成。

下面的 demo 展示只能通過純 setter 方法實現依賴注入的方式:

public class SimpleMovieLister {// the SimpleMovieLister has a dependency on the MovieFinderprivate MovieFinder movieFinder;// a setter method so that the Spring container can inject a MovieFinderpublic void setMovieFinder(MovieFinder movieFinder) {this.movieFinder = movieFinder;}// business logic that actually uses the injected MovieFinder is omitted... }

ApplicationContext 支持基于構造器和基于 Setter 方法來對它所管理的 bean 進行依賴注入,同時也支持兩種混合使用。

你可以BeanDefinition的形式配置依賴,結合 PropertyEditor 實例,將屬性從一種形式轉化為另一種形式。然而,絕大多數 Spring 用戶并不會以編程的方式直接使用這些類,而是使用 XML 的bean definition、注解式組件(即 @Component、@Controller等等)、或在@Configuration 標記的類中使用 @Bean 的形式(即 Java Config)。這些元信息依然會在內部轉化為 BeanDefinition 實例,并且被用于加載整個 Spring IOC 容器實例。

選擇構造器 DI 還是 Setter DI?

由于可以混合使用基于構造器和基于setter的DI,對于強制依賴項使用構造器,而對于可選依賴項使用setter方法或配置方法,這是一個很好的經驗法則。請注意,在setter方法上使用@Required注釋可以使屬性成為必需的依賴項,但是,更好的方式是使用構造器然后對參數進行編程驗證。

Spring 團隊更提倡使用構造器注入,因為這樣可以將應用程序組件實現為不可變對象,并確保所需的依賴項不為空。此外,構造器注入的組件總是會返回完全初始化的狀態給調用代碼。另外提一句,根據馬丁福勒的重構原理,攜帶大量參數的構造器是一種“壞味道”,這意味著類可能有太多的職責,應當適當進行重構,分離一部分依賴。

Setter注入應該主要用于可選的依賴項,這些依賴項可以在類中分配合理的默認值。否則,必須在代碼使用依賴項的任何地方執行非空檢查。setter注入的一個好處是,setter方法使該類的對象易于稍后重新配置或重新注入。

5.2 依賴解析處理

容器會以下面的方式執行 bean 依賴解析操作:

1、ApplicationContext 通過使用 bean 配置信息(可以是 XML、注解、Java Config)進行創建和初始化。

2、對于每一個 bean ,它的依賴描述為屬性、構造器函數參數、或者靜態工廠方法中的其中一種形式。這些依賴會當 bean 真正創建的時候提供給 bean。

3、每個屬性或構造函數參數都是要設置的值的實際定義,或者是對容器中另一個bean的引用。

4、作為值的每個屬性或構造函數參數都從其指定的格式轉換為該屬性或構造函數參數的實際類型。默認情況下,Spring可以將字符串格式提供的值轉換為所有內置類型,比如int、long、string、boolean等等。

Spring 容器會在創建時驗證每個 bean 的配置。但只有在真正實例化 bean 的時候才會為這些 bean 屬性賦值。容器在創建之初,會將 bean 創建為 單例(singleton-scoped)且設置為“預實例化”(默認)。Scope 被定義在 Bean Scopes。否則,bean 只有在被請求時才會創建。創建 bean 可能會導致一系列的 bean 都被創建,因為bean 本身需要依賴,而依賴也需要依賴。這些依賴項的解析工作可能會在其后發生,即第一次創建受影響的 bean 時。(Creation of a bean potentially causes a graph of beans to be created, as the bean’s dependencies and its dependencies' dependencies (and so on) are created and assigned. Note that resolution mismatches among those dependencies may show up late?—?that is, on first creation of the affected bean.)

循環依賴

如果你使用顯式構造器注入,可能會出現無法解析的循環依賴問題。

例如,A 對象需要通過構造器注入一個 B 對象,并且 B 也需要通過構造器注入一個 A 對象。如果使用 spring 容器配置 A 和 B 注入彼此,那么 IOC 容器會在運行時檢測到這樣的循環引用,并拋出 BeanCurrentlyInCreationException。

一個可能的解決方案是編輯源碼,將構造器注入改為 setter。要么,避免使用構造器注入,只允許使用 setter 注入。換句話說,盡管并不推薦 setter 注入,但 setter 注入卻可以解決循環依賴問題。

和典型的應用場景不同,循環依賴問題描述的是 bean A 和 bean B 之間,強制其中一個要在完全初始化之前注入到另一個對象中(這是典型的先有雞還是先有蛋的問題)。

Spring 容器可以在加載時檢測配置問題,例如引用了不存在的 bean 和循環依賴問題。當 bean 被真正創建之時,Spring 會盡可能晚的為屬性賦值和解析依賴。也就是說,當你請求一個對象時,如果創建該對象或其依賴發生了問題,Spring 容器會晚一點產生一個異常,例如一個找不到或無效的屬性,就會拋出一個異常。這些都是一些潛在的延遲問題,這也是為什么 ApplicationContext 實現要默認采用預實例化單例 bean 的原因。在實際需要之前創建這些bean需要花費一些前期時間和內存,但可以在創建 ApplicationContext 之時發現配置問題,而不是稍后發現。但也可以覆蓋這個默認行為,以便單例 bean 可以惰性地初始化,而不是預先實例化。

如果不存在循環依賴關系,那么當一個或多個協作bean被注入到依賴bean中時,每個協作bean在被注入到依賴bean之前都已被完全配置。也就是說,如果bean A依賴于bean B,那么容器會在調用bean A上的setter方法之前完全配置bean B。換句話說,這個 bean B 會被實例化(如果它不是預實例化的),它的依賴會設置完畢,它的相關生命周期回調方法(例如配置的init 方法或 InitializingBean 回調方法)都會被調用執行。

以下代碼是使用 xml 描述的 setter 注入:

<bean id="exampleBean" class="examples.ExampleBean"><!-- setter injection using the nested ref element --><property name="beanOne"><ref bean="anotherExampleBean"/></property><!-- setter injection using the neater ref attribute --><property name="beanTwo" ref="yetAnotherBean"/><property name="integerProperty" value="1"/> </bean><bean id="anotherExampleBean" class="examples.AnotherBean"/> <bean id="yetAnotherBean" class="examples.YetAnotherBean"/> public class ExampleBean {// a private constructorprivate ExampleBean(...) {...}// a static factory method; the arguments to this method can be// considered the dependencies of the bean that is returned,// regardless of how those arguments are actually used.public static ExampleBean createInstance (AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {ExampleBean eb = new ExampleBean (...);// some other operations...return eb;} }

5.3 depends-on

如果一個 bean 是另一個 bean 的依賴,通常意味著這個 bean 需要作為一個屬性設置到 另一個 bean 中。一般你可以使用 <ref> 標簽配置這樣的信息。但是,有時候 bean 之間的依賴關系并不那么直接。例如,當一個靜態初始化器需要注冊時,例如 數據庫驅動注冊。那么 depends-on 屬性就可以顯式地強制在初始化使用此元素的bean之前初始化一個或多個bean。如下所示:

<bean id="beanOne" class="ExampleBean" depends-on="manager"/> <bean id="manager" class="ManagerBean" />

當需要描述多個依賴時,可以這樣寫:

<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao"><property name="manager" ref="manager" /> </bean><bean id="manager" class="ManagerBean" /> <bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />

提示:depends-on屬性既可以指定初始化階段依賴項,也可以指定對應的銷毀階段依賴項(僅在單例bean中)。在銷毀給定bean本身之前,首先銷毀與給定bean定義依賴關系的依賴bean。因此,依賴還可以控制關機順序。

5.4 懶加載 Bean

默認情況,作為初始化過程的一部分,ApplicationContext 的實現會餓漢式創建和配置所有單例 bean。通常,這種預實例化的操作是好的,因為這樣可以立刻發現配置中或者環境中的問題和錯誤,而不是幾小時甚至幾天后。當不適合用這種方式時,你可以將 bean definition 設置為懶加載來避免預實例化。一個懶加載 bean 會告訴 IOC 容器,只在第一次請求對象的時候創建對象,而不是一啟動的時候。如下設置:

<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/> <bean name="not.lazy" class="com.something.AnotherBean"/>

但是注意,如果懶加載 bean 恰好是非懶加載 bean 的依賴時,那么IOC 容器會忽略懶加載屬性,而直接在啟動時創建這個懶加載 bean,因為它必須滿足作為一個依賴的身份。

也就是說,延遲初始化設置會由于被其他非延遲初始化bean依賴而失效!

另外還可以通過使用<beans/>元素的 default-lazy-init 屬性來控制容器級別的延遲初始化,如下面的示例所示:

<beans default-lazy-init="true"><!-- no beans will be pre-instantiated... --> </beans>

5.5 自動裝配依賴

Spring 容器可以自動裝配協作者之間的關系。自動裝配有以下優點:

1、自動裝配可以顯著減少指定屬性或構造函數參數的需要。

2、自動裝配可以隨著對象的發展更新配置。例如,如果需要向類添加依賴項,則無需修改配置即可自動滿足該依賴項。因此,自動裝配在開發過程中特別有用。

5.6 方法注入(Method Injection)

大多數應用場景,容器中的 bean 都是單例的。當一個單例 bean 需要和另一個單例 bean 協同工作,或一個非單例 bean 需要和另一個非單例bean 協同工作,你一般會將依賴定義為另一個 bean 的屬性。

但當 bean 的生命周期不同時,就會出現問題。

假設單例對象 A 需要使用非單例(原型)對象 B,可能在 A 的每個方法調用上。容器只會創建 A 一次,這樣也就只會有一次機會為屬性賦值。那么容器無法每次在 A 需要使用 B 的時候都實例化一個 B 提供給 A。

一種解決方法是,放棄一定的控制反轉。你可以令 A 實現ApplicationContextAware 接口來讓 A 知曉容器的存在,并且在每次 A 需要使用 B 的時候,通過 getBean("B") 來請求一個新的 B 對象。如下所示:

public class CommandManager implements ApplicationContextAware {private ApplicationContext applicationContext;public Object process(Map commandState) {// grab a new instance of the appropriate CommandCommand command = createCommand();// set the state on the (hopefully brand new) Command instancecommand.setState(commandState);return command.execute();}protected Command createCommand() {// notice the Spring API dependency!return this.applicationContext.getBean("command", Command.class);}public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;} }

這種方式并不可取,因為業務代碼知道了 Spring 框架,并與之耦合。方法注入是一個IOC 容器的高級特性,可以讓你干凈利落的處理這樣的情況。

【《Spring揭秘》bean的scope使用陷阱:

依賴于“prototype”對象的客戶端對象第一次從容器中取得該prototype對象后,會一直持有該對象引用。因此需要注意,后續從客戶端對象中使用的該“prototype”對象總是同一個,即prototype只針對每次從容器中取得才可返回新對象

方法注入(Method Injection)的含義是,容器通過指定的方法將bean注入

普通的getBean方法會使容器帶有侵入性,因此,當我們獲取prototype對象時,往往會結合方法注入來使用,指定某個方法代替getBean從容器中取得prototype對象。

其底層邏輯是Spring會通過Cglib動態代理該類并重寫該注入方法,因此,Spring要求聲明的注入方法需要符合固定格式:

<public|protected> [abstract] <return-type> injectMethodName(no-args);

對于一般的getter方法,就符合這樣的格式。因此,可以通過<lookup-method>標簽告知Spring容器,使用getXxx()方法完成指定bean對象的注入:

<bean id=”b” class=”...B” singleton=”false”></bean> <bean id=”a” class=”...A”><lookup-method name=”getB” bean=”b”/> </bean>

lookup-method標簽的name屬性指定需要注入的方法名,bean屬性指定需要注入的對象。這樣,在每次調用a.getB()的時候,程序都會向容器請求一個新的B對象并返回。

六、Bean Scope 作用域

scope用來描述容器中的對象的限定場景或存活時間

打個比方:我們都處于社會(容器)中,如果把中學教師作為一個類定義,那么當容器初始化這些類之后,中學教師只能局限在中學這樣的場景中。中學,就可以看做中學教師的scope。——《Spring揭秘》

當你創建一個 bean definition ,你實際上是創建了一個用于實例化的食譜(或處方)。這個“食譜”的思想很重要,因為這意味著,就像類一樣,你可以從一個單獨的食譜中創建許多對象

Spring 中支持 6 種scope,singleton和prototype是Spring最開始最遲的兩種scope類型,在Spring2.0 之后又加入了另外四種只在支持 web 的ApplicationContext中使用的scope類型。你也可以自定義 scope:

Scope描述

singleton

(默認) Scopes a single bean definition to a single object instance for each Spring IoC container.

prototype

Scopes a single bean definition to any number of object instances.

request

Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring?ApplicationContext.

session

Scopes a single bean definition to the lifecycle of an HTTP?Session. Only valid in the context of a web-aware Spring?ApplicationContext.

application

Scopes a single bean definition to the lifecycle of a?ServletContext. Only valid in the context of a web-aware Spring?ApplicationContext.

websocket

Scopes a single bean definition to the lifecycle of a?WebSocket. Only valid in the context of a web-aware Spring?ApplicationContext.

【《Spring揭秘》以下給出部分關鍵scope類型的精簡說明:

1、singleton:首先,同一個IOC容器只存在一個singleton的bean。其次,容器啟動時創建,第一次請求時初始化,容器不退出,對象就就一直存在。

2、prototype:每次重新生成一個新的對象給請求方。實例化、屬性設置等工作由容器負責,返回后,容器不再擁有該對象的引用,請求方需自行負責后續(如銷毀)生命周期活動。通常,聲明為prototype的bean都含有一些可變狀態。

3、request:WebApplicationContext會為每個http請求創建一個scope為request的對象,請求結束,對象的生命周期也將結束。從不嚴格的意義上說,request可以看做是prototype的一種特例,除了場景更加具體,語義上差不多。

4、session:容器會為每個會話創建scope為session的實例。與request相比,除了更長的存活時間,其他方面沒什么差別。】?

6.1 單例作用域

單例很好理解,即只有一個實例對象,spring 容器在每次請求該 bean 的時候都只返回同一個對象。

換句話說,當你定義了一個 bean ,且它的 scope 屬性是 singleton,那么 IOC 容器只會為其創建一個實例。這個實例被存儲在一個用于存儲這種單例 bean 的緩存區,后續所有的請求或引用,都會返回已緩存的單例對象。下圖展示了單例作用域的工作模式:

Spring 中的單例 bean 的概念與GoF的單例模式有些不同。GoF 單例對對象的作用域進行硬編碼,這樣每個類加載器都會創建一個且只有一個特定類的實例。Spring單例的作用域最好描述為每個容器和每個bean。這意味著,如果你在單個Spring容器中為特定類定義了一個bean,那么Spring容器將創建由該 bean 定義的類的一個且僅一個實例。

<bean id="accountService" class="com.something.DefaultAccountService"/><!-- the following is equivalent, though redundant (singleton scope is the default) --> <bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>

6.2 原型作用域

原型作用域的 bean 會在每次請求后返回一個新的 bean 實例。你應該對所有有狀態的 bean 使用原型作用域,對無狀態的 bean 使用單例作用域。下圖展示了原型作用域:

數據訪問對象(DAO)通常不配置為原型,因為典型的DAO不持有任何會話狀態。

與其他作用域相比,Spring不管理原型bean的完整生命周期。容器實例化、配置和組裝原型對象并將其交給客戶端,而不進一步記錄該原型實例。因此,盡管初始化的回調方法在所有對象上都被調用,但在原型的情況下,配置的銷毀回調不會被調用。為了讓 Spring 容器釋放被原型 bean 所持有的資源,請使用自定義bean的后置處理器(bean post-processor),它持有一個需要被清理的 bean的引用。

在一些方面,Spring 容器的原型bean 被視為 Java new 操作符的替代品。

6.3 單例bean依賴原型bean

當使用原型 bean 作為單例 bean 的依賴項時,請注意,依賴關系是在實例化時解析的。因此,如果你將一個原型 bean 依賴式地注入到一個 單例 bean 中,那么就會創建一個新的原型 bean 然后注入到單例 bean中。原型實例是提供給單例 bean的唯一實例。

但是,假設你想讓單例 bean 在運行時反復依賴原型 bean ,你不能依賴式地注入原型 bean,因為注入只會發生一次,即只會在IOC 容器實例化該單例 bean 并解析、注入它的依賴時。如果想在運行時多次請求原型 bean ,請使用方法注入(前面有介紹)。

6.4 Request、Session、Application? 和 WebSocket 作用域

參考:Request, Session, Application, and WebSocket Scopes

6.5 自定義作用域

bean 的 Scope 機制是可擴展的。你可以定義你自己的作用域,或重新定義現有的作用域,但是后者并不推薦。而且,你無法重寫內建的 singleton 和 prototype 作用域。

為了能夠將你自定義的 scope 整合到 IOC 容器,你必須實現 org.springframework.beans.factory.config.Scope 接口。

Scope 接口包含 4 個方法,可以從作用域中獲取對象,從作用域中移除對象,也可以銷毀他們。

以 Session 作用域的實現為例,會返回一個 session 作用域的 bean(如果它不存在,方法就會返回一個新的 bean 實例,然后將它綁定到 session 上,以便后續訪問)。

Object get(String name, ObjectFactory<?> objectFactory)

更多內容參考:Custom Scopes

七、Bean 的生命周期與 Aware

Spring 提供了一些接口用來幫助開發者自定義 bean 的一些性質。這一節主要講解:

生命周期回調;ApplicationContextAware 和 BeanNameAware;以及其他的 Aware 接口。

7.1 生命周期回調

要與容器對 bean 的生命周期管理交互,你需要實現 InitializingBean 接口和?DisposableBean 接口。容器對前者調用afterPropertiesSet(),對后者調用destroy(),讓bean在初始化和銷毀bean時執行某些操作。

在內部,Spring框架使用BeanPostProcessor處理任何回調接口的實現,它可以找到并調用適當的方法。如果你需要定制特性或Spring默認不提供的其他生命周期行為,可以自己實現BeanPostProcessor。除了初始化和銷毀回調之外,spring管理的對象還可以實現生命周期接口,以便這些對象可以參與啟動和關閉過程,這是由容器自己的生命周期驅動的。

初始化回調

org.springframework.beans.factory.InitializingBean 接口在容器設置了bean的所有必要屬性之后,讓bean執行初始化工作。該接口只有一個方法:

void afterPropertiesSet() throws Exception;

我們不建議你使用 InitializingBean 接口,因為它令業務代碼與 Spring 框架建立了不必要的耦合。還有另一種方式,我們更建議使用 @PostConstruct 注解或指定一個POJO 初始化方法。在 xml 形式的配置中,你可以使用 init-method 屬性來指定該方法的名稱,該方法需要具備無返回值(void)、無參數(no-arguments)的特點。如果使用 JavaConfig ,你可以使用 @Bean 中的 initMethod 屬性。

<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/> public class ExampleBean {public void init() {// do some initialization work} }

上面的例子和下面的例子效果完全相同,以下是實現 InitializingBean 接口的版本:

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/> public class AnotherExampleBean implements InitializingBean {@Overridepublic void afterPropertiesSet() {// do some initialization work} }

但是,更推薦第一個例子,因為更解耦。

銷毀回調

和初始化回調類似,Spring 同樣提供了兩種實現方式,既可以實現?DisposableBean 接口,也可以直接在 配置中指定 destroy-method 屬性。以下片段分別是實現?DisposableBean 接口和 destroy-method 屬性配置兩種不同方式,請任選其一:

---XML配置: <bean id="exampleInitBean" class="examples.AnotherExampleBean"/> ---Java 代碼: public class AnotherExampleBean implements DisposableBean {@Overridepublic void destroy() {// do some destruction work (like releasing pooled connections)} } ---XML配置: <bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/> ---Java 代碼: public class ExampleBean {public void cleanup() {// do some destruction work (like releasing pooled connections)} }

默認的初始化和銷毀方法

不需要實現 InitializingBean 接口,也不需要指定 init-method 屬性,Spring 提供了以約定命名為基礎的初始化和銷毀回調方法。

只需要在 bean 中加入 init()、destroy() 方法,Spring 就會自動查找并執行對應生命周期的回調。例如 init() 方法:

public class DefaultBlogService implements BlogService {private BlogDao blogDao;public void setBlogDao(BlogDao blogDao) {this.blogDao = blogDao;}// this is (unsurprisingly) the initialization callback methodpublic void init() {if (this.blogDao == null) {throw new IllegalStateException("The [blogDao] property must be set.");}} }

那么在配置中就不需要顯式聲明任何初始化回調方法:

<bean id="blogService" class="com.something.DefaultBlogService"><property name="blogDao" ref="blogDao" /> </bean>

Spring 容器保證在 bean 配置完全部依賴后,會立即執行配置好的初始化回調方法。因此,初始化回調是在原始 bean 上調用的,這意味著 AOP 攔截器等還沒有使用到 bean 。

混合使用生命周期機制

Spring 2.5 之后,你有三種選擇來控制 bean 生命周期行為:

1、InitializingBean 和 DisposableBean 回調接口

2、自定義 init() 和 destroy() 方法

3、@PostConstruct 和 @PreDestroy 注解。你可以混合這些機制來控制一個給定的 bean 。

如果 bean 配置了使用不同的初始化方法的多個生命周期機制,它們會以下面的順序調用:

1、@PostConstruct 注解標記的方法

2、InitializingBean回調接口定義的afterPropertiesSet()

3、自定義的 init() 方法

銷毀方法以下面的順序執行:

1、@PreDestroy 注解標記的方法

2、DisposableBean 回調接口定義的 destroy()

3、自定義的 destroy() 方法

啟動和關閉回調

Lifecycle 接口為任何具有它們自己的生命周期需求的對象定義了一些基本方法(例如啟動或停止一些后臺處理):

public interface Lifecycle {void start();void stop();boolean isRunning(); }

任何由Spring 管理的對象都可以實現 Lifecycle 接口。當 ApplicationContext 接收到開始或停止信息時(例如,在運行過程中的 stop/restart 場景),就會將這些請求串聯到對應上下文中定義的 Lifecycle 實現上。這是通過探測 LifecycleProcessor 來實現的,如下所示:

public interface LifecycleProcessor extends Lifecycle {void onRefresh();void onClose(); }

啟動的調用順序和關閉的調用順序可能很重要。如果兩個對象之間存在依賴關系,依賴端要在依賴項之后開始,在依賴項之前停止。然而,有時候這種直接的依賴關系并不清晰。你可能只知道某個對象應該在另一個對象之前啟動。這種情況下, SmartLifecycle 接口就派上用場了,它是 Phased 接口的子接口:

public interface Phased {int getPhase(); }public interface SmartLifecycle extends Lifecycle, Phased {boolean isAutoStartup();void stop(Runnable callback); }

當啟動時,具有最低相位的對象先開始。當停止時,順序則反過來。因此,如果一個對象實現了 SmartLifecycle 并且它的 getPhase() 方法返回一個 Integer.MIN_VALUE ,那么它就會第一個啟動,最后一個停止。相反的,如果 getPhase() 的值返回Integer.MAX_VALUE ,則表明這個對象應該最后一個啟動,并且第一個停止。當考慮phase值時,同樣重要的是要知道對于任何沒有實現SmartLifecycle的“正常”生命周期對象,其默認的phase是0。因此,任何負相位值都表示一個對象應該在那些標準組件之前開始(并在它們之后停止)。對于任何正相位值,情況正好相反。

7.2 ApplicationContextAware 和 BeanNameAware

當 ApplicationContext 創建了一個實現了 ApplicationContextAware 接口的 bean 實例,那么該實例同樣也會拿到一個 ApplicationContext 的引用。以下是 ApplicationContextAware 接口:

public interface ApplicationContextAware {void setApplicationContext(ApplicationContext applicationContext) throws BeansException; }

這樣, bean 就可以通過 ApplicationContext 接口或將引用轉化為它的已知子類(如ConfigurableApplicationContext)以編程的方式來操作創建它們的 ApplicationContext 對象。

當 ApplicationContext 創建了一個實現了 BeanNameAware 接口的 bean 實例,那么該實例也可以拿到一個由關聯的 bean definition 定義的 bean name。下面代碼片段是 BeanNameAware 接口:

public interface BeanNameAware {void setBeanName(String name) throws BeansException; }

這個回調方法會在填充普通bean屬性之后,在初始化回調(如InitializingBean、afterPropertiesSet或自定義初始化方法)之前調用執行。

7.3 其他的 Aware 接口

除了 ApplicationContextAware 和 BeanNameAware,Spring 提供了廣泛的感知回調接口,好讓 bean 能夠指示容器,它們需要一個基礎設施依賴。作為一般規則,名稱表示依賴類型。下表總結了最有用的 Aware 接口:

名稱注入的依賴詳細描述

ApplicationContextAware

Declaring?ApplicationContext.

ApplicationContextAware?and?BeanNameAware

ApplicationEventPublisherAware

Event publisher of the enclosing?ApplicationContext.

Additional Capabilities of the?ApplicationContext

BeanClassLoaderAware

Class loader used to load the bean classes.

Instantiating Beans

BeanFactoryAware

Declaring?BeanFactory.

ApplicationContextAware?and?BeanNameAware

BeanNameAware

Name of the declaring bean. bean 的名稱

ApplicationContextAware?and?BeanNameAware

BootstrapContextAware

Resource adapter?BootstrapContext?the container runs in. Typically available only in JCA-aware?ApplicationContext?instances.

JCA CCI

LoadTimeWeaverAware

Defined weaver for processing class definition at load time.

Load-time Weaving with AspectJ in the Spring Framework

MessageSourceAware

Configured strategy for resolving messages (with support for parametrization and internationalization).

Additional Capabilities of the?ApplicationContext

NotificationPublisherAware

Spring JMX notification publisher.

Notifications

ResourceLoaderAware

Configured loader for low-level access to resources.

Resources

ServletConfigAware

Current?ServletConfig?the container runs in. Valid only in a web-aware Spring?ApplicationContext.

Spring MVC

ServletContextAware

Current?ServletContext?the container runs in. Valid only in a web-aware Spring?ApplicationContext.

Spring MVC

注意,使用這些接口會將你的代碼與 spring api 進行耦合,且不符合控制反轉的風格。因此,我們建議將它們用于需要對容器進行編程訪問的基礎設施bean。

八、容器擴展性指導

一般,應用開發者不需要繼承 ApplicationContext 的實現類。而是通過插入特殊的繼承接口來擴展IOC 容器。

8.1 使用 BeanPostProcessor 來自定義 Bean

BeanPostProcessor,Bean 的后置處理器接口定義了回調方法,你可以實現該接口并提供你的初始化 bean 的邏輯(也可以重寫容器的默認實現)、依賴解析邏輯等等。如果你想在 容器完成實例化、裝配、初始化 bean 之后實現一些自定義邏輯,你可以插入一個或多個自定義的 BeanPostProcessor 實現。

你可以配置多個 BeanPostProcessor 實例,并且通過設置 order 屬性來控制這些實例的執行順序。只有當 BeanPostProcessor 實現了 Ordered 接口才可以設置該屬性。如果你需要自定義 BeanPostProcessor,你就也需要考慮實現 Ordered 接口。

BeanPostProcessor 實例基于 bean 實例執行操作。也就是說,IOC 容器實例化一個 bean ,然后 BeanPostProcessor 才能工作。

如果在一個容器中定義BeanPostProcessor,那么它只對該容器中的bean進行后處理,也就是說,BeanPostProcessor 的作用域是單獨的容器。

BeanPostProcessor 接口由兩個回調方法構成。當一個類在容器中注冊為一個 post-processor ,那么它的每一個實例,在容器初始化方法之前和 bean 初始化回調之后,都會從容器獲得一個回調。后置處理器可以對bean實例采取任何操作,包括完全忽略回調。bean 后置處理器通常檢查回調接口,或者用代理包裝bean。為了提供代理包裝邏輯,一些Spring AOP基礎設施類被實現為bean 后置處理器。

ApplicationContext 會自動檢測到 bean 中實現了 BeanPostProcessor 接口。ApplicationContext 會將這些 bean 注冊為 post-processor,以便后面在 bean 創建的時候進行調用。bean 的后置處理器可以像其他 bean 一樣部署在容器中。

注意,當使用 @Bean 聲明了一個 BeanPostProcessor,返回值類型應該是后置處理器的實現類,至少也應該是 BeanPostProcessor 接口類型,清楚地表明該 bean 的后置處理器特性。否則, ApplicationContext 無法自動檢測到它。由于BeanPostProcessor需要盡早實例化,以便應用于上下文中其他bean的初始化,因此這種早期類型檢測非常關鍵

編程式注冊 BeanPostProcessor?

雖然推薦的 BeanPostProcessor 注冊方式是通過 ApplicationContext 自動檢測,但你依然可以通過一個 ConfigurableBeanFactory的 addBeanPostProcessor() 方法以編程的方式注冊后置處理器。當你需要在注冊前計算條件邏輯,或在跨層次結構的上下文復制后置處理器時,這可能非常有用。但是注意,以編程方式注冊后置處理器不遵從 Ordered 接口。那么注冊順序就決定了執行順序。而且也要注意,編程式注冊的后置處理器永遠在自動檢測注冊的后置處理器之前執行,會忽略任何顯式的順序聲明。

BeanPostProcessor 實例和 AOP 自動代理

實現了 BeanPostProcessor 接口的類會被容器特殊對待。所有的 BeanPostProcessor 實例和直接引用的 bean,都會在容器啟動時實例化,并作為 ApplicationContext 啟動階段的一個特殊部分。

Next, all?BeanPostProcessor?instances are registered in a sorted fashion and applied to all further beans in the container. Because AOP auto-proxying is implemented as a?BeanPostProcessor?itself, neither?BeanPostProcessor?instances nor the beans they directly reference are eligible for auto-proxying and, thus, do not have aspects woven into them.

For any such bean, you should see an informational log message:?Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying).

If you have beans wired into your?BeanPostProcessor?by using autowiring or?@Resource?(which may fall back to autowiring), Spring might access unexpected beans when searching for type-matching dependency candidates and, therefore, make them ineligible for auto-proxying or other kinds of bean post-processing. For example, if you have a dependency annotated with?@Resource?where the field or setter name does not directly correspond to the declared name of a bean and no name attribute is used, Spring accesses other beans for matching them by type.

8.2 使用 BeanFactoryPostProcessor 來自定義配置數據

在語義上,BeanFactoryPostProcessor 和 BeanPostProcessor 相似,只有一個不同點,那就是 BeanFactoryPostProcessor 在 bean 的配置數據上進行操作。也就是說,Spring IOC 容器允許 BeanFactoryPostProcessor 讀取配置數據,并在容器實例化任何 bean (除了BeanFactoryPostProcessor實例)之前改變配置。

你可以配置多個 BeanFactoryPostProcessor 實例,也設置 order 屬性來可以控制運行順序,但也必須要實現 Ordered 接口才可以。

如果你想改變實際的 bean 實例,那么你需要使用 BeanPostProcessor(如前所述)。雖然在BeanFactoryPostProcessor中使用bean實例在技術上是可行的(例如,通過使用BeanFactory.getBean()),但是這樣做會導致過早的bean實例化,違反標準的容器生命周期。這可能會導致負面的副作用,比如繞過bean的后處理。

另外,BeanFactoryPostProcessor實例的作用域為每個容器。這只有在使用容器層次結構時才有用。如果您在一個容器中定義了BeanFactoryPostProcessor,那么它只應用于該容器中的bean定義。一個容器中的Bean定義不會被另一個容器中的BeanFactoryPostProcessor實例進行后處理,即使這兩個容器屬于同一層次結構。

當Bean 工廠后置處理器在 ApplicationContext 中聲明,它將自動執行,以便對定義容器的配置數據執行更改。Spring包括許多預定義的bean工廠后處理器,如 PropertyOverrideConfigurer 和 PropertySourcesPlaceholderConfigurer。當然也可以自定義。

ApplicationContext自動檢測部署到其中實現BeanFactoryPostProcessor接口的任何bean。在適當的時候,它將這些bean用作bean工廠的后置處理器。可以像部署任何其他bean一樣部署這些后處理bean。

九、注解式容器配置

注解要比XML更適合配置Spring嗎?

簡單的回答是:看情況。詳細的回答是,每種方式都有其利弊,通常,這取決于開發者覺得那種方式更適合。

由于其定義方式,注解在其聲明中提供了大量上下文,從而使配置更短、更簡潔。但是,XML擅長在不改動源代碼或重新編譯組件的情況下連接組件。有些開發者喜歡將配置貼近源碼,但也有一些人認為這樣帶注解的類已不再是 POJO ,而且配置也會變得更分散,難以控制。

但不論那種方式,Spring 都可以適應,甚至是混用。值得說明的是,通過 Java Config 的引入,Spring 可以讓注解以一種非侵入式的方式進行配置,不需要接觸目標組件。

注解式配置為 xml 配置提供了另一種選擇。與 xml 不同,開發者可以將配置以注解的方式放到組件類上、方法上、域上。例如 Spring 2.0 引入了 @Required 注解,這讓配置必須屬性成為可能。Spring 2.5 使遵循相同的通用方法來驅動依賴注入成為可能。從本質上,@Autowired注解提供了與Autowiring協作器中描述的相同的功能,但是擁有更細粒度的控制和更廣泛的適用性。Spring 2.5 也增加了對 JSR-250 注解的支持,如 @PostConstruct 和 @PreDestroy。Spring 3.0 增加了對 JSR-330(Java 依賴注入)的支持,包含了 javax.inject 包,如 @Inject 和 @Named。

提示:注釋注入在XML注入之前執行。這樣,xml 配置就可以對同時使用這兩種方式的屬性覆蓋掉注解配置。

和往常一樣,你可以單獨地注冊 bean 定義,但也可以通過通過下面的標簽隱式地注冊到基于 xml 配置的Spring 容器中(注意包含的 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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsd"><context:annotation-config/></beans>

(隱式注冊的后置處理器包括 AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor、PersistenceAnnotationBeanPostProcessor、和前面提到的 RequiredAnnotationBeanPostProcessor)

提示:<context:annotation-config/> 只查找定義在同一個應用程序 context 上下文的 bean 上的注解。也就是說,如果你為DispatcherServlet 在 WebApplicationContext 中配置了<context:annotation-config>標簽,那么它只檢查你的 controller 中 @Autowired 的 bean,而不會檢查 service。

9.1 @Required

@Required 注解可以放在 setter 方法上,例如:

public class SimpleMovieLister {private MovieFinder movieFinder;@Requiredpublic void setMovieFinder(MovieFinder movieFinder) {this.movieFinder = movieFinder;} }

這個注解表示必須在配置時填充受影響的bean屬性,要么通過顯式地 bean definition 要么通過 自動注入。如果 bean 屬性沒有成功注入,那么容器就會拋出一個異常。這種餓漢式明確的失敗,避免了空指針的發生。我們仍然建議將斷言放到bean類本身中(例如,放到init方法中)。這樣做會強制執行那些必需的引用和值,即使你在容器之外使用該類。

提示: @Required 注解在 Spring 5.1 被正式廢棄,我們更支持使用構造器注入必需配置(或者自定義實現 InitializingBean.afterPropertiesSet() 和 setter 方法)

9.2 使用 @Autowired

你可以在構造器上使用 @Autowired,例如:

public class MovieRecommender {private final CustomerPreferenceDao customerPreferenceDao;@Autowiredpublic MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {this.customerPreferenceDao = customerPreferenceDao;} }

提示:從Spring 4.3 開始,如果目標 bean 只定義了一個構造器,就沒必要將@Autowired 注解標記在這樣的構造器上。然而,如果有兩個或多個構造器,且沒有默認構造器,那么至少要在一個構造器上標記該注解,以便告訴容器要使用哪個。

你也可以將@Autowired 注解使用在 setter 方法上:

public class SimpleMovieLister {private MovieFinder movieFinder;@Autowiredpublic void setMovieFinder(MovieFinder movieFinder) {this.movieFinder = movieFinder;} }

也可以將@Autowired 注解放在任意方法名的多參方法上:

public class MovieRecommender {private MovieCatalog movieCatalog;private CustomerPreferenceDao customerPreferenceDao;@Autowiredpublic void prepare(MovieCatalog movieCatalog,CustomerPreferenceDao customerPreferenceDao) {this.movieCatalog = movieCatalog;this.customerPreferenceDao = customerPreferenceDao;} }

你可以可以將@Autowired 放在域上,甚至是混用在構造器上:

public class MovieRecommender {private final CustomerPreferenceDao customerPreferenceDao;@Autowiredprivate MovieCatalog movieCatalog;@Autowiredpublic MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {this.customerPreferenceDao = customerPreferenceDao;} }

你可以通過向需要該類型數組的字段或方法添加@Autowired注解來指示Spring從ApplicationContext中提供所有特定類型的bean:

public class MovieRecommender {@Autowiredprivate MovieCatalog[] movieCatalogs; }

類型集合也可以:

public class MovieRecommender {private Set<MovieCatalog> movieCatalogs;@Autowiredpublic void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {this.movieCatalogs = movieCatalogs;}}

提示:如果想讓數組或集合中的bean 有序,你可以讓目標 bean 實現 Ordered 接口或使用 @Order 注解或使用 標準的@Priority 注解。否則,只能以注冊時的順序來排序這些 bean。

你可以在目標類型上指定 @Order 注解,或者 @Bean 方法上。 @Order 注解的 value 可能影響注入點的優先級,但不能影響單例的啟動順序,單例的啟動順序是由依賴關系和@DependsOn 決定的。

注意,標準 @Priority 注解不能使用在 @Bean 級別上,因為它無法聲明在方法上。

Map 的實例也可以自動注入,只要key 類型是 String。Map 的key 應該保存對應 bean 的名字,如下所示:

public class MovieRecommender {private Map<String, MovieCatalog> movieCatalogs;@Autowiredpublic void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {this.movieCatalogs = movieCatalogs;} }

默認情況,如果在注入點沒有匹配的候選 bean 可用,那么自動注入就會失敗。聲明的如果是數組、集合或 Map,至少需要有一個匹配的 bean 元素。

默認標記 @Autowired 注入的依賴是必需項。你可以改變這種默認設置,使框架跳過非必需依賴注入,如下所示:

public class SimpleMovieLister {private MovieFinder movieFinder;@Autowired(required = false)public void setMovieFinder(MovieFinder movieFinder) {this.movieFinder = movieFinder;} }

如果依賴項不可用,或其中一個依賴項的 setter 方法有多個參數,那么非必需方法將根本不會被調用。在這種情況下,將根本不填充非必需字段,保留其默認值。

從 5.0 開始,你也可以使用 @Nullable 注解:

public class SimpleMovieLister {@Autowiredpublic void setMovieFinder(@Nullable MovieFinder movieFinder) {...} }

你也可以使用 @Autowired 注解注入這些已知的框架相關依賴:BeanFactory、ApplicationContext、Environment、ResourceLoader、ApplicationEventPublisher,和 MessageSource。這些接口及其子接口,例如ConfigurableApplicationContext 或 ResourcePatternResolver,已經自動解析完畢,不需要特殊的設置:

public class MovieRecommender {@Autowiredprivate ApplicationContext context;public MovieRecommender() {} }

提示:@Autowired、@Inject、@Value? 和 @Resource 注解,都會被 Spring 自己的 BeanPostProcessor 實現來處理。也就是說,你無法在自己的BeanPostProcessor或BeanFactoryPostProcessor類型(如果有的話)中應用這些注釋。這些類型必須通過使用XML或Spring @Bean方法顯式地“連接起來”。

9.3 使用 @Primary 來微調基于注解的自動注入(updated on 2020-10-11)

因為通過類型來完成自動注入可能會有多個“候選項”(注:如接口類型可能會有多個實現子類),因此也就有必要有更多的控制手段。一種方法是通過 @Primary 注解來完成此項操作。@Primary 表示當單值依賴(single-valued dependency)注入時有多個候選項,那么應該只提供一個特定的 bean 引用如果這些候選項中有一個確定的“主要的”(primary)bean ,那么它就會是那個被注入的值。

考慮下面的用例:

@Configuration public class MovieConfiguration {@Bean@Primarypublic MovieCatalog firstMovieCatalog() { ... }@Beanpublic MovieCatalog secondMovieCatalog() { ... }}

基于上面的配置用例,下面的 MovieRecommender 電影推薦類就會注入 firstMovieCatalog 類型的 bean:

public class MovieRecommender {@Autowiredprivate 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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsd"><context:annotation-config/><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>

9.4 使用 @Qualifier 微調基于注解的自動注入(updated on 2020-10-11)

@Primary 是一種可以處理通過類型判斷自動注入時多個候選項只注入主要候選項的有效方式(或者可以描述為默認候選項注入)。當你需要更有控制力的方式來影響注入 bean 的匹配過程時,可以使用 Spring 提供的 @Qualifier 注解。你可以為 @Qualifier 的 value 屬性指定特殊的值,從而縮小類型匹配的范圍,以便為每個參數選擇特定的bean。例如下面用例:

public class MovieRecommender {@Autowired@Qualifier("main")private MovieCatalog movieCatalog;}

或者用在構造器上,指定注入的參數類型:

public class MovieRecommender {private MovieCatalog movieCatalog;private CustomerPreferenceDao customerPreferenceDao;@Autowiredpublic void prepare(@Qualifier("main") MovieCatalog movieCatalog,CustomerPreferenceDao customerPreferenceDao) {// ...} }

對應的 bean 定義如下:

<?xml version="1.0" encoding="UTF-8"?> <beans ... ><context:annotation-config/><bean class="example.SimpleMovieCatalog"><qualifier value="main"/><!-- inject any dependencies required by this bean --></bean><bean class="example.SimpleMovieCatalog"><qualifier value="action"/> <!-- inject any dependencies required by this bean --></bean><bean id="movieRecommender" class="example.MovieRecommender"/></beans>

對于降級匹配(fallback match),bean 的名稱被視為一個默認的限定值(qualifier value)。這樣,你可以將 bean 的 id 屬性定義為 “main” 而不是使用內嵌的<qualifier> 標簽,來達到同樣的匹配效果。

然而,雖然你可以使用這種約定,通過名稱來引入一個特定的 bean,但 @Autowired 基本上還是一個以類型驅動(type-driven)的注入方式,只是帶有可選的語義限定符(optional semantic qualifier)。這也就是說,限定值即使帶有名稱降級匹配,但也總是在類型匹配集中具有收縮語義(narrowing semantics)。它們在語義上并不表達唯一的 bean 的 id。

好的限定值是 main、EMEA、或 persistent,表示獨立于bean id 的特定組件的特征,在匿名bean定義(如前面示例中的bean定義)的情況下,可以自動生成。

@Qualifier 同樣可以用于類型化集合的注入情況。例如, Set<MovieCatalog>。在這種情況下,所有匹配的 bean,根據聲明的限定值,都會被注入到集合中。這也就是說,限定值不必唯一,它們組成了某個過濾條件。例如,你可以定義多個 MovieCatalog 類的 bean,使用相同的限定值“action”,那么所有這些 bean 都會被注入到聲明了 @Qualifier("action") 的 Set<MoiveCatalog> 集合中。

提示:在類型匹配候選中選擇指定名稱的?bean,不需要在注入點使用 @Qualifier 注解。如果沒有其他解析指示器(例如 @Qualifier 或 @Primary),對于非唯一依賴情況,Spring將注入點名稱(即字段名稱或參數名稱)與目標 bean 名稱匹配,并選擇同名的候選對象,也就是說字段名稱本身就默認是一種限定值

也就是說,如果你打算通過名稱來表示注解注入,不要都用 @Autowired,雖然它的確可以在類型匹配的候選中選擇指定名稱的 bean。相反地,應該使用 JSR-250 標準的 @Resource 注解,這個注解在語義上定義為通過惟一名稱標識特定的目標組件,聲明的類型與匹配過程無關。@Autowired 注解有著相當不同的語義:在類型匹配結束后,指定的 String 類型限定符值只會在類型匹配的候選項中選擇。

對于定義為集合、Map 或 數組類型的 bean,@Resource 是一個很好的解決方式,通過唯一的名稱引入集合或數組 bean。也就是說,從 Spring 4.3 開始,你依然可以使用 @Autowired 的類型匹配算法來匹配集合、Map 和 數組類型,只要元素類型信息保存在@Bean返回類型簽名或集合繼承層次結構中。這種情況下,你可以使用限定符在相同類型的集合中進行選擇,如前一段所述。

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

總結

以上是生活随笔為你收集整理的Spring —— IoC 容器详解的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

国产精品久久久久久久久久久久冷 | 我要色综合天天 | 欧美日韩xxx | 国产高清精 | 五月婷婷久久丁香 | 伊人网站 | 999在线观看视频 | 韩日电影在线免费看 | 久久精品9 | 一本一本久久a久久精品牛牛影视 | 成人免费看片98欧美 | 亚洲精品国产片 | 日本精品视频在线 | 亚洲精品国产电影 | 黄色在线小网站 | 五月婷婷在线综合 | 成人免费看视频 | 97韩国电影 | 最新午夜 | 国产不卡在线观看视频 | 女人魂免费观看 | 五月婷婷丁香六月 | 久久精品一二区 | 精品一区二区三区香蕉蜜桃 | 91av视频观看| 成人福利在线观看 | 国产91九色蝌蚪 | 2019中文字幕网站 | 一区二区三区四区精品视频 | 91日韩精品一区 | 亚洲国产婷婷 | 在线观看网站你懂的 | 超碰在线人人艹 | 色综合久久综合网 | 一本一本久久a久久精品综合 | 麻豆传媒视频在线免费观看 | 久久视频一区二区 | 麻豆免费精品视频 | 丁香六月久久综合狠狠色 | 九色porny真实丨国产18 | 国产精品久久久久久久久久久久午 | 亚洲精品91天天久久人人 | 国产美女无遮挡永久免费 | 狠狠地操| 久久精品91久久久久久再现 | 国产高清网站 | 激情在线免费视频 | 久久五月情影视 | 亚洲国产中文在线 | 黄色午夜 | 日韩成人邪恶影片 | 天天色天天操天天爽 | 亚洲欧美精品一区二区 | 国产精品综合av一区二区国产馆 | 国产资源中文字幕 | 欧美日韩有码 | 国产视频亚洲 | 欧美激情综合五月 | 99在线精品免费视频九九视 | 久久99这里只有精品 | 九九热免费观看 | 96精品视频 | 欧美男男激情videos | 操高跟美女 | 国产三级视频在线 | 96精品视频 | 日本三级中文字幕在线观看 | 久久久精品影视 | 国产精品久久久久久久久蜜臀 | 成人久久毛片 | 日韩免费中文 | 日本激情视频中文字幕 | 精品中文字幕视频 | 人人狠狠综合久久亚洲 | 国产麻豆精品久久一二三 | aa级黄色大片 | 五月天久久久久 | 久久久精品国产免费观看同学 | 国产玖玖精品视频 | 一级一片免费视频 | 免费观看一区二区 | 亚洲永久免费av | 91av看片| 免费h漫在线观看 | 欧美一级免费在线 | 91中文字幕永久在线 | 欧美一级特黄aaaaaa大片在线观看 | 欧美视频日韩视频 | 国产午夜精品一区二区三区嫩草 | 超碰av免费 | 欧美日韩xx | 色综合久久网 | 婷婷色资源 | 亚洲黄色在线播放 | 日韩免费在线视频观看 | 欧美日韩综合在线 | 国产高清视频色在线www | 五月香视频在线观看 | 免费在线观看日韩 | 九九综合九九 | 欧美另类v | 成人网在线免费视频 | 在线观看www视频 | 日韩av网页| 久久高清国产视频 | 亚洲欧洲国产精品 | 五月天久久久久久 | 九九激情视频 | 天天色草 | a色视频 | 91看片在线看片 | 成人app在线播放 | 亚洲精品456在线播放第一页 | 色99在线| 国产高清免费在线观看 | 日本特黄特色aaa大片免费 | 91av福利视频 | 国产精品一区二区久久精品爱涩 | 色婷婷六月天 | 国产精品久久久久久久久蜜臀 | 超碰免费观看 | 91精品国| 99在线视频精品 | 中文字幕av在线播放 | 欧美在线视频精品 | 91漂亮少妇露脸在线播放 | 亚洲精品大片www | 国产一二区视频 | 国产精品一区二区免费视频 | 亚洲91网站 | 久久久久 免费视频 | 国产精品成久久久久三级 | 人人爽人人做 | bayu135国产精品视频 | 国产成人精品午夜在线播放 | 美女在线免费观看视频 | 亚洲精品乱码久久久久久久久久 | 免费在线观看黄 | 亚洲午夜精品久久久久久久久久久久 | 久久91久久久久麻豆精品 | 天天做日日爱夜夜爽 | 在线播放日韩av | 久色免费视频 | 久久免费av电影 | 激情五月婷婷综合 | 成人免费视频网址 | 免费观看成年人视频 | 在线看不卡av | 国产精品久久久久一区二区 | 久久视频精品在线 | 国产精品第二页 | 色视频网址 | 亚洲精品国偷自产在线91正片 | 国产黄色免费在线观看 | 亚洲视频在线看 | 日韩精品一区二区在线观看视频 | 夜夜操综合网 | 国产精品99在线观看 | 91麻豆国产 | 玖玖在线资源 | 夜夜视频欧洲 | 超碰97国产精品人人cao | 99在线精品免费视频九九视 | 中文字幕永久免费 | 天天做天天爽 | 亚洲精品av中文字幕在线在线 | 日韩欧美高清视频在线观看 | 91成人精品国产刺激国语对白 | 中文字幕色在线视频 | 久久久国产影院 | 狠狠久久婷婷 | 日韩69视频| 久久中文视频 | 国产精品久久久久久久av电影 | 9999精品| 五月色丁香 | www.夜夜干.com| 久久99国产综合精品免费 | 99久久精品免费看国产免费软件 | 久久久亚洲精华液 | 免费精品国产va自在自线 | 成人丝袜 | 在线观看av小说 | 天天骚夜夜操 | 国产在线1区 | 在线观看av不卡 | 黄色毛片视频免费观看中文 | 99热这里只有精品久久 | 最近中文字幕在线中文高清版 | 91av视频免费观看 | 91麻豆网站 | 69av国产| 99国产成+人+综合+亚洲 欧美 | 日韩视频精品在线 | 国产在线看一区 | 三级动态视频在线观看 | 欧美精品一二 | 伊人久久精品久久亚洲一区 | 国产精品久久久久久久久蜜臀 | 日韩精品一区二区三区中文字幕 | 精品欧美一区二区精品久久 | 日韩免费视频线观看 | 国产小视频在线免费观看视频 | 国产亚洲高清视频 | 91精品国产自产在线观看永久 | 密桃av在线| 日韩黄色一级电影 | 日韩免费区 | a在线免费 | 日韩欧美在线观看一区二区三区 | 高潮毛片无遮挡高清免费 | 操老逼免费视频 | 国产色拍拍拍拍在线精品 | 色网站在线看 | 国产第一页精品 | 国产不卡网站 | 在线观看av大片 | 亚洲成人av在线 | 久久久精品国产免费观看一区二区 | 成人综合婷婷国产精品久久免费 | 成年人视频在线 | 国产一区二区视频在线播放 | 亚洲热久久 | 激情网站免费观看 | 成人免费网站在线观看 | 国产99久久 | 精品国产一区二区在线 | 91视频电影 | 99精品视频免费观看 | 亚洲乱码国产乱码精品天美传媒 | 最近在线中文字幕 | 国产成人精品免高潮在线观看 | 黄色福利视频网站 | 免费亚洲成人 | 久草电影免费在线观看 | av免费看av | 区一区二区三区中文字幕 | 国产精品理论片 | 国内久久久久 | 日韩精品在线看 | 丁香婷婷激情国产高清秒播 | 丝袜美腿在线视频 | 丰满少妇在线观看 | 国产一卡二卡在线 | 国产视频观看 | 久久99国产一区二区三区 | 亚洲欧美偷拍另类 | 91精品在线观看视频 | 久久久久国产精品一区二区 | 91中文字幕在线视频 | 99这里只有 | 四虎影院在线观看av | 天天操天天干天天操天天干 | 欧美综合在线视频 | 欧美极品久久 | 亚洲视频六区 | www.夜夜 | 亚洲欧美成人综合 | 免费观看一区 | 成人电影毛片 | 色婷婷啪啪免费在线电影观看 | 中文字幕一区在线观看视频 | 日韩欧美视频在线观看免费 | 黄色一级大片在线免费看产 | 久久久久综合精品福利啪啪 | 三级黄色理论片 | 在线观看免费成人av | 中文字幕 国产 一区 | 国产亚洲综合精品 | 五月婷婷色 | 精品亚洲二区 | 久青草影院 | 人人插人人做 | 免费精品在线视频 | 日韩高清二区 | 九九九视频在线 | 亚洲狠狠婷婷综合久久久 | 久久精品视频网站 | 国产极品尤物在线 | 国产美女网站在线观看 | av免费网站在线观看 | 久草在线精品观看 | 四虎在线免费观看视频 | 国产精品黄色 | 六月丁香激情综合 | 精品嫩模福利一区二区蜜臀 | 久久久国产影院 | 午夜视频在线观看一区 | 操操操日日日干干干 | 日韩区欠美精品av视频 | 国产一级片免费播放 | 五月视频 | 日韩1级片| 成人三级网站在线观看 | 久久精品国产免费 | av+在线播放在线播放 | 成人午夜影院在线观看 | 黄网站www | 在线 高清 中文字幕 | 久草在线一免费新视频 | 欧美人人爱 | 天天射射天天 | 国产精品一区二区av影院萌芽 | 在线观看成人国产 | 欧美日韩国产高清视频 | 狠狠亚洲 | 欧美日韩国内在线 | 日韩激情视频 | av永久网址 | 久久久久久网址 | 黄色av成人在线 | 中文字幕资源在线 | 精品国产_亚洲人成在线 | 国产不卡一| 国产中文在线视频 | 久久综合狠狠综合久久激情 | 97超碰成人在线 | 在线视频观看你懂的 | 亚洲天堂毛片 | 久久久久久网站 | 国产精品欧美一区二区三区不卡 | 四虎精品成人免费网站 | 日韩成人免费在线观看 | 黄色的视频网站 | 激情五月六月婷婷 | 成人国产精品免费观看 | 日韩欧美一区二区三区黑寡妇 | 久久精品看片 | 日韩理论片在线观看 | 在线观看视频一区二区三区 | 中文免费在线观看 | 免费亚洲视频 | 国产三级视频 | www.天堂av| 精品视频免费播放 | 国产精品18久久久久久vr | 中文字幕乱码在线播放 | 日韩综合视频在线观看 | 色婷婷www | 久久久麻豆 | 国产精品一区二区三区99 | www.xxxx欧美| 亚洲免费公开视频 | 日韩在线免费小视频 | 91精品国产欧美一区二区成人 | 黄色大全免费网站 | 国产又粗又猛又黄视频 | 天天爽天天碰狠狠添 | bbbbb女女女女女bbbbb国产 | 欧美一二三四在线 | 亚洲三级在线 | 亚洲 欧美 日韩 综合 | 日韩久久精品一区二区 | 国色天香在线 | 天天干天天射天天爽 | www.com黄| 亚洲dvd| 欧美日韩中文国产一区发布 | 国产亚洲成av片在线观看 | 一级片视频在线 | 黄色成人小视频 | 人人狠狠 | 香蕉视频在线播放 | 日韩精品播放 | 最近日本中文字幕 | 超碰在线天天 | 在线观看国产一区 | 天天色天天爱天天射综合 | 亚洲欧洲精品视频 | 丁香视频五月 | 少妇性色午夜淫片aaaze | 久久久久这里只有精品 | 天天操夜夜操 | 国产三级午夜理伦三级 | 日本三级中文字幕在线观看 | 999视频在线播放 | 亚洲在线激情 | 亚洲精品成人av在线 | 91中文字幕永久在线 | 91丨九色丨国产在线观看 | 天天曰夜夜操 | 成年人在线电影 | 日日干日日操 | 香蕉国产91 | 日韩在线视频观看免费 | 天天色草 | 天天草综合 | 五月亚洲 | 亚洲欧美精品一区 | 天天天天综合 | 日韩精品免费一区二区在线观看 | 91资源在线 | 日韩av影视 | 国产成人精品久久 | 日韩网站在线免费观看 | 看片在线亚洲 | 久草在线高清 | 丁香六月五月婷婷 | 天天射天天干天天操 | 成年人看片 | 国产一区二区观看 | 人人擦 | 国语对白少妇爽91 | 少妇bbbb搡bbbb搡bbbb | av短片在线 | 欧美日韩视频免费看 | 国产精品一区二区免费 | 日日夜夜人人天天 | 91精品视频一区二区三区 | 国产精品网站一区二区三区 | 91资源在线观看 | 精品超碰| 日产av在线播放 | 欧美日韩午夜在线 | 国产精品夜夜夜一区二区三区尤 | 色一级片 | 久草在线手机观看 | 中文字幕 国产精品 | 在线v片免费观看视频 | 久久精品99久久 | 日日干天天爽 | 国产亚洲视频在线观看 | 婷婷av综合 | 在线观看的a站 | 一区二区三区四区精品视频 | 欧美日韩国产综合一区二区 | 日韩中文字幕网站 | 99热99热 | 色偷偷88888欧美精品久久久 | 中文字幕丰满人伦在线 | 日韩欧美69 | 97精品国自产拍在线观看 | 国产一区二区三区四区在线 | 丁香在线 | 91| 五月天婷婷免费视频 | 国产成人精品久久 | 国产色综合天天综合网 | 国产精品毛片 | 一区二区三区在线看 | 丁香六月婷婷开心 | 最近的中文字幕大全免费版 | 国产福利午夜 | 欧美日韩精品网站 | 国产一级大片在线观看 | 国产第一页在线观看 | 在线播放精品一区二区三区 | 日韩精品一区电影 | 免费不卡中文字幕视频 | 国产经典av | 中文字幕在线播放一区 | 亚洲福利精品 | 亚洲国产精品久久久 | 在线看黄色的网站 | 久久福利在线 | 97精产国品一二三产区在线 | 狠狠干夜夜 | 色视频 在线 | 欧美精品久久久久久久久老牛影院 | jizz999| 亚洲国产精品视频 | 999视频精品| 国产成人一二三 | 欧美不卡视频在线 | 久草久草视频 | 香蕉视频一级 | 色综合久久久久综合体桃花网 | 天天干天天干天天操 | 欧美日韩精品二区第二页 | 免费a视频在线观看 | 国产精品一区二区av影院萌芽 | 黄色h在线观看 | 午夜免费久久看 | 天天操夜夜做 | 国产精品九九九 | 亚洲va欧洲va国产va不卡 | 青青河边草免费观看 | 国产高清小视频 | a黄色影院 | 日日草夜夜操 | 成人精品国产免费网站 | 91丨九色丨91啦蝌蚪老版 | 在线免费看黄色 | 亚洲欧美在线视频免费 | 日日躁天天躁 | 探花视频网站 | 激情小说网站亚洲综合网 | 欧美高清视频不卡网 | 在线观看的av网站 | 婷婷午夜激情 | 在线成人短视频 | 一区二区三区免费播放 | 中文字幕成人 | 在线观看视频精品 | 青春草国产视频 | 在线精品视频在线观看高清 | 日本中文字幕免费观看 | 国语自产偷拍精品视频偷 | 国产自在线 | 96精品在线 | 日韩深夜在线观看 | 日韩高清毛片 | 国产精品18久久久久久不卡孕妇 | www.夜夜草 | 操操碰 | 中文字幕日本特黄aa毛片 | 精品夜夜嗨av一区二区三区 | 蜜臀av在线一区二区三区 | 日韩在线网 | 九九热在线观看 | 香蕉色综合 | 精品一区精品二区高清 | 91精品第一页 | 国产成人精品一区二区三区网站观看 | 国产精品美女视频 | 欧美 日韩 国产 中文字幕 | 精品国产免费人成在线观看 | 亚洲天堂激情 | 中文字幕五区 | 色狠狠一区二区 | 中文字幕一区二区三区精华液 | 视频一区在线免费观看 | 久久热首页| 国产精品av在线 | 最新午夜 | 国产精品久久久久久一二三四五 | 久久久久久毛片精品免费不卡 | 日韩理论电影在线 | 狠狠操狠狠插 | 国产精品福利小视频 | 69精品在线 | 人人爽人人av | 国产精品久久久久毛片大屁完整版 | 日韩在线看片 | 国产视频丨精品|在线观看 国产精品久久久久久久久久久久午夜 | 亚洲丁香日韩 | 狠狠色狠狠色终合网 | 亚洲综合色丁香婷婷六月图片 | 人人视频网站 | 国产69精品久久99的直播节目 | 久久99精品视频 | 91精品国产欧美一区二区成人 | 亚洲精品乱码久久久久久按摩 | 91精品电影 | 亚洲美女免费视频 | 天堂在线免费视频 | 国产最新91| 国产精品久久久久久久午夜片 | 国产999精品 | 国产又粗又猛又黄又爽 | 国产高清视频色在线www | 在线观看免费成人 | 日韩午夜在线观看 | 久久久久国产成人免费精品免费 | 国产成人三级一区二区在线观看一 | 亚州欧美视频 | 国色天香在线观看 | 国产又黄又硬又爽 | 久久中文网| 在线观看av网 | 国产一二三四在线观看视频 | 日本夜夜草视频网站 | 久久久免费观看 | 欧美精品少妇xxxxx喷水 | 国产精品一区二区三区免费视频 | 亚洲在线黄色 | 欧美一区在线观看视频 | h视频在线看 | 久久久久99999 | 91视频高清完整版 | 中文字幕在线观看免费 | 91九色精品女同系列 | 精品国内自产拍在线观看视频 | 九九九九九九精品 | 麻豆91在线看 | 久久99这里只有精品 | 欧美精品一二三 | 久久精视频 | 国产 字幕 制服 中文 在线 | 免费在线观看av电影 | 日韩精品在线播放 | 中文字幕一区二区在线观看 | 婷婷视频在线观看 | 88av视频| 在线看国产精品 | 日韩精品视频一二三 | 久久99精品国产99久久 | 亚洲欧美成aⅴ人在线观看 四虎在线观看 | 婷婷视频在线观看 | 超碰97.com| 久久久久久国产一区二区三区 | 黄色三级网站在线观看 | 波多野结衣在线观看一区二区三区 | 欧美色伊人 | 日本不卡123 | 91精品国产99久久久久 | 韩国一区二区三区视频 | 人人爽人人爽 | 久久久久亚洲精品 | 日韩精品中文字幕在线不卡尤物 | 在线激情网 | 久久国产精品电影 | 欧美成人精品欧美一级乱黄 | 国产精品女教师 | 超碰人人91| 久久精品国产成人精品 | 免费视频久久 | 欧美日韩精品在线免费观看 | 亚洲免费在线看 | 免费观看全黄做爰大片国产 | 在线黄网站 | 青草草在线视频 | 成人av直播 | 国内精品在线一区 | 国产免费视频在线 | 亚洲丝袜一区二区 | 精品亚洲午夜久久久久91 | 精品嫩模福利一区二区蜜臀 | 色是在线视频 | 日韩电影一区二区三区 | 91chinesexxx| 丰满少妇对白在线偷拍 | 日本一区二区高清不卡 | 日韩激情片在线观看 | 日韩视频在线不卡 | 国产一卡在线 | 国产视频久久久 | www178ccom视频在线 | 国内精品久久久久影院男同志 | 亚洲 中文字幕av | 在线观看成人小视频 | 在线v | 999国产精品视频 | 人人草在线观看 | 国产精品尤物视频 | 午夜视频导航 | 黄色成人影院 | 国产精品美女视频 | av免费看电影 | 国产一级片免费视频 | 精品久久久久久综合日本 | 在线观看免费av片 | 日本h视频在线观看 | 精品国产一区二区三区在线观看 | 日韩精品久久中文字幕 | 亚洲国产中文字幕在线观看 | 欧美一级片播放 | 久久综合中文字幕 | 天天操夜夜做 | 亚洲伦理一区 | 涩涩成人在线 | 国产日韩欧美视频 | 精品久久网 | 夜夜夜精品 | 国产精品网址在线观看 | 在线免费观看黄网站 | 日本精品视频在线 | 日本黄网站 | 日韩一级黄色大片 | 国产美女视频 | 麻豆传媒电影在线观看 | av在线电影播放 | 国产一二三区av | 国产很黄很色的视频 | 99视频这里只有 | 久久免费视频这里只有精品 | 在线免费国产 | 亚洲成人av在线电影 | 日韩激情免费视频 | 亚洲精品久久久久中文字幕m男 | 国产一区二区视频在线播放 | a级免费观看 | 缴情综合网五月天 | 国产99久久久精品视频 | 久久亚洲国产精品 | 91av免费观看 | 亚洲精品在 | 欧美久久久久久久 | 国产 视频 高清 免费 | 精品国产一区在线观看 | 全久久久久久久久久久电影 | 五月婷婷开心 | 国产精品久久久久久一区二区三区 | 国产在线小视频 | 九九九在线观看 | 国产亚洲精品久久久久久 | 久久在线免费视频 | 黄av资源 | 国产精品毛片一区视频 | 欧美精品一区在线发布 | 国产直播av | 久久伊人国产精品 | 国产精品一区二区三区免费看 | 国产精品精品久久久 | 日精品 | 久久夜色精品国产欧美乱极品 | 麻豆一二三精选视频 | 国产精品18久久久久久首页狼 | 日韩精品中文字幕久久臀 | 夜夜躁狠狠躁 | 五月婷婷视频在线 | 国产视频欧美视频 | 久草视频在线新免费 | 最近最新中文字幕 | 91成人网在线 | 在线观看免费版高清版 | 国产小视频在线观看 | 伊人成人久久 | 欧美精品九九99久久 | 国产精品成人自产拍在线观看 | 日韩一区二区免费视频 | 国产精品6999成人免费视频 | 色综合久久综合网 | 欧美伦理一区 | 精品国产乱码久久久久久1区二区 | 97综合网 | 99精品视频在线观看 | 视频三区在线 | 亚洲国产免费网站 | 黄色在线观看免费 | 日韩中文字幕在线 | 国产美女免费视频 | 91精品高清| 国产精品福利午夜在线观看 | 久久久私人影院 | 国产精品亚洲人在线观看 | 91九色国产在线 | 狠狠色狠狠色综合日日小说 | 欧美高清成人 | 国产一区免费看 | 91视频91色 | 丁香婷婷色综合亚洲电影 | 日日干夜夜骑 | 天天曰视频| 99免费国产 | aaaaaa毛片 | 天天综合婷婷 | 精品国产成人 | 亚洲综合成人在线 | 日韩在线免费小视频 | 在线免费黄色av | 日韩一区二区免费视频 | 97视频网站 | 5月丁香婷婷综合 | 久久精品亚洲 | 国产色视频一区二区三区qq号 | 91视频免费 | 伊香蕉大综综综合久久啪 | 国产精品久久久久久久久久东京 | 久久官网 | 最新av在线网站 | 精品视频不卡 | 青青草在久久免费久久免费 | 91九色视频导航 | 久久er99热精品一区二区 | 久久精品国产亚洲aⅴ | 不卡视频一区二区三区 | 婷婷色狠狠 | 国产成人精品一区在线 | 国产亚洲精品久久久久久电影 | 国产成人精品999 | 毛片网站免费在线观看 | 免费欧美 | 日韩在线电影观看 | 亚洲精选视频在线 | 337p日本大胆噜噜噜噜 | 国产在线探花 | 久久精品一二三区白丝高潮 | 91在线免费观看国产 | 一区二区三高清 | 91大神精品视频在线观看 | 久草精品视频在线播放 | www.狠狠操.com | 天天色天天干天天色 | www.色午夜,com | 亚洲一级性 | 亚洲乱码国产乱码精品天美传媒 | 国产中文字幕免费 | 97视频播放 | 美女国内精品自产拍在线播放 | 国产成人黄色av | 在线观看视频福利 | 伊人va| 97国产在线视频 | 国产一区二区成人 | av中文天堂在线 | 日本三级在线观看中文字 | 国产123av | 免费在线a| 亚洲综合在线一区二区三区 | 免费久久久久久久 | 国产高清中文字幕 | 97人人精品| 久久综合日 | 久久资源总站 | 久久草在线精品 | 又黄又爽免费视频 | 日本激情视频中文字幕 | 99亚洲精品视频 | 国内成人精品视频 | av超碰在线 | 激情综合亚洲精品 | 狠狠色噜噜狠狠 | 久久69精品久久久久久久电影好 | 久久男人免费视频 | 日韩免费福利 | 亚洲日本va午夜在线电影 | 国产成人综合精品 | 麻豆视频www | 久久免费99精品久久久久久 | 国产一区在线视频播放 | 国产综合精品一区二区三区 | 欧美福利视频一区 | 久色网 | 成人免费一区二区三区在线观看 | 亚洲资源在线 | 丁香花在线观看免费完整版视频 | 国产精品网站一区二区三区 | 精品字幕 | 天天做天天爱天天爽综合网 | 国产精品免费视频观看 | 最近中文字幕高清字幕免费mv | 婷婷五月色综合 | 最近更新好看的中文字幕 | 欧美一级在线观看视频 | 国产视频在线观看一区 | 在线观看日韩国产 | 最新日韩视频在线观看 | 99久久精品网 | 亚洲三级在线播放 | 综合色天天 | 2019中文字幕第一页 | 91视频在线免费观看 | 亚洲高清av在线 | 午夜精品一区二区三区四区 | 日本激情视频中文字幕 | 国内精品视频免费 | 人人要人人澡人人爽人人dvd | 人人爽人人爽人人片av | 欧美一级电影免费观看 | 美女性爽视频国产免费app | 中国一级片在线观看 | 午夜av日韩 | 色婷婷中文 | 有码视频在线观看 | 成人黄色中文字幕 | 成人avav| 亚洲一二视频 | 国产在线观看你懂得 | 中文字幕免费观看全部电影 | 国产精品亚洲视频 | 欧美另类xxxxx | 国产精品综合在线观看 | 国产高清免费av | 成人午夜网址 | av在线观 | 91大神dom调教在线观看 | 国产色在线观看 | www激情久久 | 国产最新视频在线观看 | 99久久精品免费看国产一区二区三区 | 天天干天天做天天操 | 亚洲影院一区 | 波多野结衣视频网址 | 激情五月婷婷丁香 | 波多野结衣综合网 | 久久精品日产第一区二区三区乱码 | 亚洲热久久 | 成人avav| 欧美久久久久久久久 | 久久精品黄 | 在线精品在线 | 欧美日韩二区三区 | 国产精品一区二区麻豆 | 久久精品一区二区三 | 免费大片黄在线 | 国产婷婷视频在线 | 久久一区国产 | 中文字幕精品三级久久久 | 久久视频这里只有精品 | 成年人免费av | 日韩欧美视频在线 | www黄色软件 | 日本在线视频一区二区三区 | 国产一区二区观看 | 亚洲深夜影院 | 久久99精品国产91久久来源 | 久久精品免费播放 | 久草精品在线 | www.97视频| 国产爽视频 | 97夜夜澡人人爽人人免费 | 人人干网 | 国产精品久久久久久麻豆一区 | 亚洲无吗av | 99精品在线免费观看 | 久久综合狠狠综合久久狠狠色综合 | 久久成人18免费网站 | 欧美亚洲国产一卡 | 91在线观看黄 | 人人爱人人添 | 欧美日韩91 | 国产精品久久久久久久久久不蜜月 | 国产日韩欧美在线免费观看 | 人人看97 | h网站免费在线观看 | 91成人在线观看高潮 | 免费在线国产黄色 | 国产美女主播精品一区二区三区 | 日韩av免费观看网站 | 欧美一区二区视频97 | 日本xxxx.com | 欧美了一区在线观看 | av中文字幕剧情 | 性日韩欧美在线视频 | 日韩一区在线免费观看 | 欧美日韩在线播放 | 99riav1国产精品视频 | 99热最新网址| 天天射天| 国产综合片 | 久久精品久久99 | 人人爱爱人人 | 超碰在线最新地址 | 久久影视精品 | 精品9999| 亚洲伊人av | 九九热在线免费观看 | 亚洲天堂网在线观看视频 | 中文字幕大全 | 国产伦理一区二区 | 日韩欧美视频一区 | 91免费高清| 久久在视频 | 成人三级网站在线观看 | 女人18毛片a级毛片一区二区 | 久久人人爽人人片av | 国产一区二区精品久久 | 国产一级免费在线观看 | 亚洲天堂自拍视频 | 一区二区三区av在线 | 国产精品美女免费看 | 久久久国产精品视频 | 就要干b| 日韩毛片精品 | 午夜精品视频一区二区三区在线看 | 日日爱夜夜爱 | 丁香免费视频 | 天天天干天天天操 | 天天射综合网站 | 中文永久免费观看 | 欧美国产三区 | 免费成人在线视频网站 | 中文理论片 | 亚洲精品乱码久久久久久 | 精品久久一级片 | 亚洲精品中文字幕在线观看 | 一区二区三区视频 | 天天草天天干天天射 | 国产色视频123区 | 在线观看欧美成人 | 日韩av图片 | 亚洲欧洲精品一区 | 久久久久黄色 | 激情综合网五月激情 | 欧美在线1区 | 在线观看中文字幕视频 | 超碰人人91 | 探花视频在线版播放免费观看 | 免费视频国产 | 91chinesexxx| 欧美久久久久久久 | 久久丁香网 | 久久国产麻豆 | 欧美 另类 交 | 亚洲国产三级在线观看 | 五月婷影院 | 91成人小视频 | 欧美日韩精品在线观看视频 | 亚洲2019精品 | 久久精品99久久 | 五月婷婷中文字幕 | 91精品免费在线 | 亚洲h在线播放在线观看h | 在线免费高清一区二区三区 | 婷婷色综 | av片在线看 | 在线观看91网站 | 四虎免费在线观看 | 日韩女同av | 日韩电影黄色 | 亚洲亚洲精品在线观看 | 亚洲情影院| 玖玖在线资源 | 在线观看资源 | 国产精品igao视频网入口 | 亚洲欧美视频在线 | 久久线视频 |