javascript
Spring--IoC(1)
目錄
?
IoC容器和Bean
Container
配置元數據
Container實例化
使用Container
Bean
Bean命名
Bean實例化
構造函數實例化
靜態工廠方法實例化
實例工廠方法實例化
依賴
依賴注入
構造函數注入
setter注入
依賴處理過程
配置詳解
直接賦值
引用其他bean
內部Bean
集合
Null及空字符串值
XML短命名空間
復合屬性名稱
depends-on
延遲加載bean
自動裝配
自動裝配的限制與缺點
裝配時排除bean
方法注入
Lookup Method
replace-method注入
Bean Scope
Request, Session, Application, WebSocket Scopes
初始化Web Configuration
Request scope
Session Scope
Application Scope
自定義Scope
自定義Bean生命周期
Lifecycle Callbacks
初始化回調
銷毀回調
默認初始化回調與銷毀回調
組合生命周期機制
開啟和關閉回調
在非Web應用環境中優雅地關閉Spring Ioc窯器
ApplicationContextAware和BeanNameAware
Other Aware Interfaces
Bean定義繼承
IoC容器和Bean
Spring的核心思想是控制反轉(IoC),也被稱為依賴注入。它是一個過程,對象僅通過構造函數參數、工廠方法的參數
或把從工廠方法構造或返回的對象實例設置為屬性等方式定義他們的依賴(一起工作的對象)。然后,容器在bean被創建后注入這些依賴項。這個過程基本上是bean自身通過直接構造類或服務定位器模式等機制來控制其依賴項的實例化或位置的反過程(因此稱為控制反轉)。
核心包:org.springframework.beans 和 org.springframework.context。BeanFactory接口提供了一種高級配置機制,能夠管理任何類型的對象。applicationContext是BeanFactory的子接口。它補充了:
- 更容易與Spring的AOP功能集成
- 消息資源處理(用于國際化)
- 事件發布
- 應用層特定的上下文,如用于Web應用程序的WebApplicationContext。
BeanFactory提供了配置框架和基本功能,ApplicationContext添加了更多特定于企業的功能。
在Spring中,構成應用程序主干并由SpringIOC容器管理的對象稱為bean。bean是一個由SpringIOC容器實例化、組裝和管理的對象。否則,bean只是應用程序中許多對象之一。bean以及它們之間的依賴關系反映在容器使用的配置元數據中。
Container
org.springframework.context.applicationContext接口表示SpringIOC容器,負責實例化、配置和組裝bean。容器通過讀取配置元數據獲取關于要實例化、配置和組裝的對象的指令。配置元數據以XML、Java注解或Java代碼來表示。
Spring提供了applicationContext接口的幾個實現。在獨立應用程序中,通常創建ClassPathXMLApplicationContext或FileSystemXMLApplicationContext的實例。雖然XML是定義配置元數據的傳統格式,但您可以通過提供少量XML配置來指示容器使用Java注解或代碼作為元數據格式,以聲明性地支持對這些附加元數據格式的支持。
Spring工作原理:
配置元數據
配置元數據表示作為應用程序開發人員,如何通知Spring容器實例化、配置和組裝應用程序中的對象。配置方式包括:
- XML配置
- 基于注解的配置:Spring2.5引入了對基于注解的配置元數據的支持。
- 基于Java的配置:從Spring 3開始,Spring JavaConFig項目提供的許多特性成為核心Spring框架的一部分。因此,可以使用Java而不是XML文件來定義應用程序類外部的bean。要使用這些新功能,請參見@configuration、@bean、@import和@dependson注釋。
Spring配置由容器必須管理的至少一個且通常不止一個bean定義組成。基于XML的配置元數據將這些bean配置為頂級<beans/>元素中的<bean/>元素。Java配置通常在被@Configuration注解的類中使用@bean注解的方法。
<?xml version="1.0" encoding="UTF-8"?>
<beans? ......>
??? <bean id="..." class="...">? ?
??????? <!-- collaborators and configuration for this bean go here -->
??? </bean>
</beans>
?
id屬性是標識單個bean定義的字符串。
class屬性定義bean的類型并使用完全限定的類名。
Container實例化
Spring IoC 容器需要在應用啟動時進行實例化。在實例化過程中,IoC 容器會從各種外部資源(如本地文件系統、Java 類路徑等)加載配置元數據,提供給ApplicationContext構造函數。
?
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
組合配置元數據
當系統規模比較大時\通常會讓bean定義分到多個XML文件。
<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。所有位置路徑都是執行導入的定義文件的相對路徑,因此services.xml必須與執行導入的文件位于同一目錄或類路徑位置,而messagesource.xml和themesource.xml必須位于導入文件位置下方的資源位置。前導斜杠被忽略
可以(但不推薦)使用相對的“..”路徑引用父目錄中的文件。這樣做會創建對當前應用程序外部文件的依賴關系。特別是,對于classpath:urls(例如classpath:../services.xml),不建議使用此引用,運行時解析過程將選擇“最近”的classpath根目錄,然后查看其父目錄。類路徑配置更改可能導致選擇其他不正確的目錄。
始終可以使用完全限定的資源位置,而不是相對路徑:例如,文件:c:/config/services.xml或classpath:/config/services.xml。但是,請注意,您正在將應用程序的配置耦合到特定的絕對位置。通常情況下,最好通過在運行時根據JVM系統屬性解析的“$…”占位符,為此類絕對位置保留間接尋址。
命名空間本身提供了導入指令功能。在Spring-提供的一系列XML名稱空間中,除了純bean定義之外,還提供了其他配置功能,例如Context和Util名稱空間。
使用Container
ApplicationContext 是高級工廠的接口,能維護不同bean及其依賴項的注冊表。其提供的方法T getBean(String name, Class<T> requiredType),可以用于檢索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();
Bean
Spring Container管理多個Bean,在Container內部,Bean是通過BeanDefinition表現的。包含下列元數據:
- 包限定類名。通常是正在定義的bean的實際實現類。
- bean行為配置元素。用于說明bean在容器中的行為(范圍、生命周期回調等)。
- 對bean執行其工作所需的其他bean的引用。這些引用也稱為合作者或依賴關系。
- 要為新創建的對象設置的其他配置,例如,池的大小限制或管理連接池的bean中要使用的連接數。
元數據轉換為組成每個bean定義的一組屬性
| Property | Explained in…? |
| 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的信息的bean定義之外,applicationContext實現還允許注冊容器外部(由用戶)創建的現有對象。通過getBeanFactory()方法訪問ApplicationContext的BeanFactory,該方法返回BeanFactory的DefaultListableBeanFactory實現。DefaultListableBeanFactory通過registerSingleton(..)和RegisterBeanDefinition(..)方法支持注冊功能。但是,典型的應用程序只使用通過常規bean定義元數據定義的bean。
Bean命名
每個bean都有一個或多個標識符。這些標識符在承載bean的容器中必須是唯一的。bean通常只有一個標識符。但是,如果它需要多個名稱,則可以將額外的名稱視為別名。
在基于XML的配置元數據中,可以使用id屬性和/或name屬性來指定bean標識符。ID屬性允許您僅指定一個ID。通常,這些名稱是字母數字(“mybean”、“someservice”等),但它們也可以包含特殊字符。如果要為bean引入其他別名,還可以在name屬性中指定它們,用逗號(,),分號(;)或空格分隔。作為歷史記錄,在Spring3.1之前的版本中,id屬性被定義為xsd:id類型,這限制了可能的字符。從3.1開始,它被定義為xsd:string類型。注意,bean id的唯一性仍然由容器強制實現,盡管不再由XML解析器實現。
如果不顯式地提供名稱或ID,容器將為該bean生成一個唯一的名稱。但是,如果希望通過使用ref元素或服務定位器樣式的查找來按名稱引用該bean,則必須提供一個名稱。
Bean定義外部實現別名
使用<alias/>元素來實現。
#name屬性用于內部,alias屬性用于外部。
<alias name="fromName" alias="toName"/>
#下面3個名稱都指向同一個Bean對象。
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
Bean實例化
Bean Definition本質上是創建一個或多個對象的方法。當被詢問時,容器會查看命名bean的配方,并使用由該bean定義封裝的配置元數據來創建(或獲取)實際對象。
如果使用基于XML的配置元數據,則指定要在<bean/>元素的class屬性中實例化的對象的類型(或類)。這個類屬性(在內部是BeanDefinition實例上的類屬性)通常是強制的。可以通過以下兩種方式之一使用Class屬性:
- 類全限定名,使用new創建
- 對象工廠類全限定名,使用工廠方法創建
內部類的名稱采用binary name,例如:com.example.SomeThing$OtherThing
構造函數實例化
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
靜態工廠方法實例化
<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;
??? }
}
實例工廠方法實例化
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
??? <!-- inject any dependencies required by this locator bean -->
</bean>
#2個Bean分別通過一個examples.DefaultServiceLocator實例的2個實例方法構建。
<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 框架中,主要有以下兩種注入方式。
- 構造函數注入
- setter注入
構造函數注入
構造函數方案
package x.y;
public class ThingOne {
??? public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
??????? // ...
??? }
}
<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>
?
package examples;
public class ExampleBean {
??? // Number of years to calculate the Ultimate Answer
??? private int years;
??? // The Answer to Life, the Universe, and Everything
??? private String ultimateAnswer;
??? public ExampleBean(int years, String ultimateAnswer) {
??????? this.years = years;
??????? this.ultimateAnswer = ultimateAnswer;
??? }
}
構造器參數類型匹配
<bean id="exampleBean" class="examples.ExampleBean">
??? <constructor-arg type="int" value="7500000"/>
??? <constructor-arg type="java.lang.String" value="42"/>
</bean>
構造器參數索引
<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標志,要么使用@ConstructorProperties?
package examples;
public class ExampleBean {
??? // Fields omitted
??? @ConstructorProperties({"years", "ultimateAnswer"})
??? public ExampleBean(int years, String ultimateAnswer) {
??????? this.years = years;
??????? this.ultimateAnswer = ultimateAnswer;
??? }
}
setter注入
基于setter方法的DI 是在通過調用無參數構造函數或無參數靜態工廠方法來實例化bean 后,通過容器調用bean 的setter 方法完成的。
方式有Xml,Java注解(@Component,@Bean)
<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"/>
依賴處理過程
- ApplicationContext 根據元數據創建和初始化
- 對于每個bean,其依賴項都以屬性、構造函數參數或靜態工廠方法的參數的形式表示
- 每個屬性或構造函數參數是實際值或者是另一個bean的引用。
- 作為值的每個屬性或構造函數參數都將從其指定的格式轉換為該屬性或構造函數參數的實際類型。默認情況下,Spring可以將以字符串格式提供的值轉換為所有內置類型,如int、long、string、boolean等。
配置詳解
直接賦值
直接賦值支持字符串、原始類型的數據。
元素<property/>中的value 屬性允許以對人友好、易讀的形式配置屬性或構造參數。Spring 的便利之處就是將這些字符串的值轉換為指定的類型。
String:
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
??? <!-- results in a setDriverClassName(String) call -->
??? <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
??? <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
??? <property name="username" value="root"/>
??? <property name="password" value="masterkaoli"/>
</bean>
命名空間前綴
<beans xmlns="http://www.springframework.org/schema/beans"
??? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
??? xmlns:p="http://www.springframework.org/schema/p"
??? xsi:schemaLocation="http://www.springframework.org/schema/beans
??? https://www.springframework.org/schema/beans/spring-beans.xsd">
??? <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
??????? destroy-method="close"
??????? p:driverClassName="com.mysql.jdbc.Driver"
??????? p:url="jdbc:mysql://localhost:3306/mydb"
??????? p:username="root"
??????? p:password="masterkaoli"/>
</beans>
java.util.Properties
<bean id="mappings"
??? class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
??? <!-- typed as a java.util.Properties -->
??? <property name="properties">
??????? <value>
??????????? jdbc.driver.className=com.mysql.jdbc.Driver
??????????? jdbc.url=jdbc:mysql://localhost:3306/mydb
??????? </value>
??? </property>
</bean>
?
idref
idref元素用來將容器內其它bean的id傳給<constructor-arg/> 或 <property/>元素,同時提供錯誤驗證功能,減少配置的書寫錯誤機率。除了<idref bean=""/>,如果被引用的bean在同一個xml文件中,且bean的名字就是bean的id,除了可以使用<idref local=""/>,此屬性允許xml解析器在解析XML的時候對引用的bean進行驗證。其實idref就跟value一樣,只是將某個字符串注入到屬性或者構造函數中,只不過注入的是某個bean定義的id屬性值。
?
<bean id="theTargetBean" class="..."/>
<bean id="theClientBean" class="...">
??? <property name="targetName">
??????? <idref bean="theTargetBean"/>
??? </property>
</bean>
#等同于:
<bean id="client" class="...">
??? <property name="targetName" value="theTargetBean"/>
</bean>
?
引用其他bean
<ref>元素通過id或者name引用其他bean,可以通過bean,local,parent屬性設置。
<ref bean="someBean"/>??
parent屬性引用當前container的父container,僅存在于層次容器中。
<bean id="accountService" class="com.something.SimpleAccountService">
??? <!-- insert dependencies as required as here -->
</bean>
<bean id="accountService" <!-- bean name is the same as the parent bean -->
??? class="org.springframework.aop.framework.ProxyFactoryBean">
??? <property name="target">
??????? <ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
??? </property>
??? <!-- insert other configuration and dependencies as required here -->
</bean>
內部Bean
<bean id="outer" class="...">
??? <!-- instead of using a reference to a target bean, simply define the target bean inline -->
??? <property name="target">
??????? <bean class="com.example.Person"> <!-- this is the inner bean -->
??????????? <property name="name" value="Fiona Apple"/>
??????????? <property name="age" value="25"/>
??????? </bean>
??? </property>
</bean>
內部bean是匿名的,并且不可以被其他bean引用。
集合
<list/>, <set/>, <map/> 和 <props/> 元素對應Java集合類型:List, Set, Map, 和Properties
<bean id="moreComplexObject" class="example.ComplexObject">
??? <!-- results in a setAdminEmails(java.util.Properties) call -->
??? <property name="adminEmails">
??????? <props>
??????????? <prop key="administrator">administrator@example.org</prop>
??????????? <prop key="support">support@example.org</prop>
??????????? <prop key="development">development@example.org</prop>
??????? </props>
??? </property>
??? <!-- results in a setSomeList(java.util.List) call -->
??? <property name="someList">
??????? <list>
??????????? <value>a list element followed by a reference</value>
??????????? <ref bean="myDataSource" />
??????? </list>
??? </property>
??? <!-- results in a setSomeMap(java.util.Map) call -->
??? <property name="someMap">
??????? <map>
??????????? <entry key="an entry" value="just some string"/>
??????????? <entry key ="a ref" value-ref="myDataSource"/>
??????? </map>
??? </property>
??? <!-- results in a setSomeSet(java.util.Set) call -->
??? <property name="someSet">
??????? <set>
??????????? <value>just some string</value>
??????????? <ref bean="myDataSource" />
??????? </set>
??? </property>
</bean>
map的key或value或者是集合的value都可以配置為下列元素
bean | ref | idref | list | set | map | props | value | null
集合合并
不能合并不同類型的集合
Null及空字符串值
<bean class="ExampleBean">
??? <property name="email" value=""/>
</bean>
<bean class="ExampleBean">
??? <property name="email">
??????? <null/>
??? </property>
</bean>
?
XML短命名空間
p 命名空間令開發者可以使用bean的屬性,而不用使用嵌套的<property/>元素就能描述開發者想要注入的依賴
<beans xmlns="http://www.springframework.org/schema/beans"
??? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
??? xmlns:p="http://www.springframework.org/schema/p"
??? xsi:schemaLocation="http://www.springframework.org/schema/beans
??????? https://www.springframework.org/schema/beans/spring-beans.xsd">
??? <bean name="classic" class="com.example.ExampleBean">
??????? <property name="email" value="someone@somewhere.com"/>
??? </bean>
??? <bean name="p-namespace" class="com.example.ExampleBean"???????? p:email="someone@somewhere.com"/>
</beans>
<beans xmlns="http://www.springframework.org/schema/beans"
??? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
??? xmlns:p="http://www.springframework.org/schema/p"
??? xsi:schemaLocation="http://www.springframework.org/schema/beans
??????? https://www.springframework.org/schema/beans/spring-beans.xsd">
??? <bean name="john-classic" class="com.example.Person">
??????? <property name="name" value="John Doe"/>
??????? <property name="spouse" ref="jane"/>
??? </bean>
??? <bean name="john-modern"
??????? class="com.example.Person"
??????? p:name="John Doe"
??????? p:spouse-ref="jane"/>
??? <bean name="jane" class="com.example.Person">
??????? <property name="name" value="Jane Doe"/>
??? </bean>
</beans>
?
與p命名空間類似, c命名空間允許內聯的屬性來配置構造參數, 而不用使用constructor-arg元素。c 命名空間是在Spring 3.1 版本首次引入的.
?
<beans xmlns="http://www.springframework.org/schema/beans"
??? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
??? xmlns:c="http://www.springframework.org/schema/c"
??? xsi:schemaLocation="http://www.springframework.org/schema/beans
??????? https://www.springframework.org/schema/beans/spring-beans.xsd">
??? <bean id="beanTwo" class="x.y.ThingTwo"/>
??? <bean id="beanThree" class="x.y.ThingThree"/>
??? <!-- traditional declaration with optional argument names -->
??? <bean id="beanOne" class="x.y.ThingOne">
??????? <constructor-arg name="thingTwo" ref="beanTwo"/>
??????? <constructor-arg name="thingThree" ref="beanThree"/>
??????? <constructor-arg name="email" value="something@somewhere.com"/>
??? </bean>
??? <!-- c-namespace declaration with argument names -->
??? <bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
??????? c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>
</beans>
索引方式
<!-- c-namespace index declaration -->
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
??? c:_2="something@somewhere.com"/>
復合屬性名稱
開發者可以在配置屬性時配置復合屬性的名稱,只要確保除了最后一個屬性外,其余的屬性值都不能為Null 。
<bean id="something" class="things.ThingOne">
??? <property name="fred.bob.sammy" value="123" />??
</bean>
fred,bob屬性必須不為null。
depends-on
有時bean 之間的依賴關系不是直接關聯的,如需要調用類的靜態實例化工具來觸發, 一個典型的例子是數據庫驅動注冊。
depends-on 屬性會使明確的、強迫依賴的bean 在引用之前就會初始化
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
如果想要依賴多個bean ,可以提供多個名稱作為depends-on 的值,以逗號、空格或分號分割
<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" />
延遲加載bean
默認情況下,ApplicationContext會在實例化的過程中創建和配置所有的單例bean。開發者可以通過將bean 標記為延遲加載阻止預初始化。
延遲初始化的bean 會通知IoC 不要讓bean 預初始化,而是在被引用時才會實例化。在XML 中,可以通過<bean>元素的lazy-init屬性來控制這個行為
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>
如果一個延遲加載的bean作為另外一個非延遲加載的單例bean 的依賴而存在,延遲加載的bean仍然會在ApplicationContext 啟動時加載。因為作為單例bean的依賴,它會隨著單例bean 的實例化而實例化。
開發者可以通過使用<beans> 的default-lazy-init屬性在容器層次控制bean是否延遲初始化
<beans default-lazy-init="true">
??? <!-- no beans will be pre-instantiated... -->
</beans>
自動裝配
Spring容器可以自動裝配協作bean之間的關系。通過檢查applicationContext的內容,可以讓Spring自動解析bean的合作者(其他bean)。自動裝配具有以下優點:
- 顯著減少指定屬性或構造函數參數的需要
- 隨著對象的發展更新配置。
Xml配置使用<bean>的autowire屬性,有4種模式:
-
no:默認值,不自動,使用ref元素。不建議修改默認值。
-
byName:通過名稱
-
byType:通過類型
-
constructor:構造函數
自動裝配的限制與缺點
- property和constructor-arg顯式依賴項會覆蓋autowired。不能autowire簡單屬性,如基本類型、字符串和類(以及此類簡單屬性的數組)。這種限制是由設計造成的。
- autowired不如顯式裝配精確。
- 生成文檔的工具可能無法獲得接線信息。
- 如果沒有唯一的bean定義可用,則引發異常。
裝配時排除bean
autowire-candidate設置為false,只影響byType類型的自動裝配。<beans> 的default-autowire-candidates屬性可以設置哪些bean自動裝配,支持通配符*,?? 例如:*Repository
方法注入
大多數的bean在應用場景中都是單例的。當這個單例的bean 需要和非單例的bean聯合使用時,有可能會因為不同bean的生命周期不同而產生問題。假設單例的bean A 在每個方法調用中使用了非單例的bean B ,由于容器只會創建bean A 一次,而只有一個機會來配置屬性。那么容器就無法給bean A 每次都提供一個新的bean B 的實例。
一個解決方案就是放棄一些IoC 。開發者可以通過實現ApplicationContextAware接口,調用ApplicationContext的getBean("B")方法在bean A 需要新的實例時來獲取新的bean B實例
public class CommandManager implements ApplicationContextAware {
??? private ApplicationContext applicationContext;
??? public Object process(Map commandState) {
??????? // grab a new instance of the appropriate Command
??????? Command command = createCommand();
??????? // set the state on the (hopefully brand new) Command instance
??????? command.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的API 。這在一定程序上對Spring 框架存在耦合。那么是存還有其他方案來避免這些弊端呢?答案是肯定的。
Spring 框架提供了<lookup-method>和<replace-method>來解決上述問題。
Lookup Method
lookup-method注入是Spring 動態改變bean中方法的實現。方法執行返回的對象,是使用了CGLIB 庫的方法, 重新生成子類,重寫配置的方法和返回對象,達到動態改變的效果。
- 為了使這個動態子類化工作,SpringBean容器子類不能是final類,要重寫的方法也不能是final類。
- 單元測試具有抽象方法的類需要您自己對類進行子類化,并提供抽象方法的存根實現。
- 組件掃描還需要具體的方法,這需要具體的類別來獲取。
- 另一個關鍵限制是查找方法不適用于工廠方法,尤其不適用于配置類中的@bean方法,因為在這種情況下,容器不負責創建實例,因此無法動態創建運行時生成的子類。
?
package fiona.apple;
public abstract class CommandManager {
??? public Object process(Object commandState) {
??????? // 需要一個實例
??????? Command command = createCommand();
??????? command.setState(commandState);
??????? return command.execute();
??? }
??? //此方法返回一個實例,抽象方法,具體實現在哪里呢?
??? protected abstract Command createCommand();
}
在上面代碼中,將要被注入的方法的類為CommandManager ,被注入方法格式為:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
?
XML配置方式:
<!-- a stateful bean deployed as a prototype (non-singleton) ,如果設置成了單例,每次返回同一實例-->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
??? <!-- inject dependencies here as required -->
</bean>
<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
??? <lookup-method name="createCommand" bean="myCommand"/>
</bean>
Lookup注解方式:
public abstract class CommandManager {
??? public Object process(Object commandState) {
??????? Command command = createCommand();
??????? command.setState(commandState);
??????? return command.execute();
??? }
??? @Lookup("myCommand")
??? protected abstract Command createCommand();
}
?
replace-method注入
replaced-method 注入是Spring 動態改變bean中方法的實現。需要改變的方法,使用Spring內原有其他類(需要繼承接口org.springframework.beans.factory.support.MethodReplacer )的邏輯替換這個方法。通過改變方法執行邏輯來動態改變方法。內部實現為使用CGLIB 方法,重新生成子類,重寫配置的方法和返回對象,達到動態改變的效果。
示例:
public class MyValueCalculator {
? //computeValue方法將會被替換
??? public String computeValue(String input) {
??????? // some real code...
??? }
??? // some other methods...
}
public class ReplacementComputeValue implements MethodReplacer {
??? public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
??????? // get the input value, work with it, and return a computed result
??????? String input = (String) args[0];
??????? ...
??????? return ...;
??? }
}
XML配置:
<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
??? <!-- arbitrary method replacement -->
??? <replaced-method name="computeValue" replacer="replacementComputeValue">
<!-- arg-type的值可以為實際類型的子串,因為方法參數個數已經可以區別出大多數方法了 -->
??????? <arg-type>String</arg-type>
??? </replaced-method>
</bean>
<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>
注解配置
無
Bean Scope
Bean Scope值:
| Scope | Description |
| singleton | 單例 |
| prototype | 多例 |
| request | 每個HTTP請求一個實例 |
| session | 每個Session一個實例 |
| application | 每個Application一個實例 |
| websocket | 每個WebSocket一個實例 |
Request, Session, Application, WebSocket Scopes
request、session、application 及websocket 這幾個scope 都是只有在基于Web 的ApplicationContext實現(如XmlWebApplicationContext)中才能使用。如果開發者僅僅在常規的Spring IoC 容器(如ClassPathXmlApplicationContext )中使用這些scope ,那么將會拋出一個IllegalStateException 來說明使用了scope 。
初始化Web Configuration
為了能夠使用request 、session 、application 及websocket 這幾個scope , 需要在配置bean 之前做一些基礎的配置。而對于標準的scope,如singleton 及prototype ,是無須這些基礎的配置的。
具體如何配置取決于Servlet的環境。
如果開發者使用了Spring Web MVC 框架,那么每一個請求都會通過Spring 的DispatcherServlet 或DispatcherPortlet 來處理,也就沒有其他特殊的初始化配置c DispatcherServlet 和DispatcherPortlet 己經包含了相關的狀態。
如果使用Servlet 2.5 的Web 容器,請求不是通過Spring 的Dispatcherservlet (如JSF 或Struts )來處理,那么開發者需要注冊org.springframework.web.context.request.RequestContextListener 或ServletRequestListener。而在Servlet 3.0以后,這些都能夠通過WebApplicationlnitializer 接口來實現。如果是一些舊版本的容器,可以在web.xml中增加以下的Listener 聲明。
<web-app>
??? ...
??? <listener>
??????? <listener-class>
??????????? org.springframework.web.context.request.RequestContextListener
??????? </listener-class>
??? </listener>
??? ...
</web-app>
也可以考慮使用Spring的RequestContextFilter。Filter 的映射取決于Web 應用的配置:
<web-app>
??? ...
??? <filter>
??????? <filter-name>requestContextFilter</filter-name>
??????? <filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
??? </filter>
??? <filter-mapping>
??????? <filter-name>requestContextFilter</filter-name>
??????? <url-pattern>/*</url-pattern>
??? </filter-mapping>
??? ...
</web-app>
DispatcherServlet 、RequestContextListener 及RequestContextFilter 本質上完全一致,都是綁定請求對象到服務請求的Thread 上。這才使得bean在之后的調用鏈上可見。
Request scope
<bean id="loginAction" class="com.something.LoginAction" scope="request"/>
@RequestScope
@Component
public class LoginAction {
??? // ...
}
Session Scope
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
@SessionScope
@Component
public class UserPreferences {
??? // ...
}
?
Application Scope
<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>
@ApplicationScope
@Component
public class AppPreferences {
??? // ...
}
當把一個scope的bean注入到一個更長scope的bean時,建議用AOP Proxy代替。使用<aop:scoped-proxy/>
自定義Scope
可以通過實現org.springframework.beans.factory.config.Scope 接口實現自定義scope
自定義Bean生命周期
spring框架提供了許多接口,您可以使用這些接口來定制bean的生命周期。
- Lifecycle Callbacks
- ApplicationContextAware和BeanNameAware
- Other Aware Interfaces
Lifecycle Callbacks
可以通過實現InitializingBean 和DisposableBean接口來管理container的生命周期,容器調用前者afterpropertiesset(),為調用后者的destroy(),以便bean在初始化和銷毀bean時執行某些操作。使用這些接口的一個弊端就是與Spring API 產生了耦合。
JSR-250 的@PostConstruct 和@PreDestroy 注解就是現代Spring 應用生命周期回調的最佳實踐。使用這些注解意味著bean 不會再耦合在Spring 特定的接口上。
如果開發者不想使用JSR-250的注解,仍然可以考慮使用init-method 和destroy-method 的定義來解耦Spring 接口。
從內部來說,Spring 框架使用BeanPostProcessor的實現來處理接口的回調, BeanPostProcessor能夠找到并調用合適的方法。如果開發者需要定制一些Spring并不直接提供的生命周期行為,可以考慮自行實現一個BeanPostProcessor。
除了初始化回調和銷毀回調, Spring 管理的對象也實現了Lifecycle 接口讓管理的對象在容器的生命周期內啟動或關閉。
初始化回調
org.springframework.beans.factory.InitializingBean接口允許bean在所有必要依賴配置完成后執行初始化bean的操作。InitializingBean接口中定義了一個方法:
void afterPropertiesSet() throws Exception;
建議開發者不要使用InitializingBean接口,否則會將代碼耦合到Spring 的特定接口之上。
Xml方式:
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {
??? public void init() {
??????? // do some initialization work
??? }
}
Java注解:
@Configuration
public class AppConfig {
??? @Bean(initMethod = "init")
??? public BeanOne beanOne() {
??????? return new BeanOne();
??? }
}
銷毀回調
實現了org.springframework.beans.factory.DisposableBean接口的bean就能讓容器通過回調來銷毀bean 所引用的資源。DisposableBean接口包含一個方法destroy(),
void destroy() throws Exception;
不建議開發者使用DisposableBean 回調接口, 否則會將開發者的代碼耦合到Spring 代碼上。
Xml方式:
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean {
??? public void cleanup() {
??????? // do some destruction work (like releasing pooled connections)
??? }
}
java注解:
@Configuration
public class AppConfig {
??? @Bean(initMethod = "init")
??? public BeanOne beanOne() {
??????? return new BeanOne();
??? }
??? @Bean(destroyMethod = "cleanup")
??? public BeanTwo beanTwo() {
??????? return new BeanTwo();
??? }
}
默認初始化回調與銷毀回調
Spring可以自動識別init()方法作為初始化回調方法,destroy()方法作為銷毀回調方法,
<beans default-init-method="init">
??? <bean id="blogService" class="com.something.DefaultBlogService">
??????? <property name="blogDao" ref="blogDao" />
??? </bean>
</beans>
當配置了default-init-method屬性,則如果一個類定義了指定的方法,則會在何時的時候調用此初始化回調。類似,default-destroy-method輸定定義了默認銷毀回調。默認回調可以被init-method,destroy-method設置覆蓋。
spring容器保證在bean提供所有依賴項之后立即調用已配置的初始化回調。因此,初始化回調在原始bean引用調用,這意味著aop攔截器等尚未應用于bean。
組合生命周期機制
在Spring 2.5 版本之后,開發者有以下3種選擇來控制bean 的生命周期行為。
- InitializingBean和DisposableBean接口
- init()和destroy()
- @PostConstruct和@PreDestroy
如果一個bean 配置了多個生命周期機制,并且含有不同的方法名,執行的順序如下。
- ①包含@PostConstruct 注解的方法。
- ②在InitializingBean 接口中的afterPropertiesSet()方法。
- ③自定義的init()方法。
銷毀方法的執行順序與初始化的執行順序相同。
- ①包含@PreDestroy 注解的方法。
- ②在DisposableBean接口中的destroy()方法。
- ③自定義的destory()方法。
開啟和關閉回調
在非Web應用環境中優雅地關閉Spring Ioc窯器
如果開發者在非Web應用環境使用Spring IoC 容器,如在桌面客戶端的環境下,開發者需要在JVM上注冊一個關閉的鉤子,以確保在關閉Spring IoC容器時能夠調用相關的銷毀方法來釋放掉引用的資源。當然,開發者也必須要正確地配置和實現那些銷毀回調
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class Boot {
??? public static void main(final String[] args) throws Exception {
??????? ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
??????? // add a shutdown hook for the above context...
??????? ctx.registerShutdownHook();
??????? // app runs here...
??????? // main method exits, hook is called prior to the app shutting down...
??? }
}
ApplicationContextAware和BeanNameAware
當ApplicationContext 在創建實現了org.springframework.context.ApplicationContextAware接口的對象時,該對象的實例會包含一個到ApplicationContext 的引用。
public interface ApplicationContextAware {
??? void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
這樣bean就能夠通過編程的方式操作和創建ApplicationContext
當ApplicationContext 創建了一個實現了org.springframework.beans.factory.BeanNameAware接口的類,那么這個類就可以針對其名稱進行配置
public interface BeanNameAware {
??? void setBeanName(String name) throws BeansException;
}
這個回調的調用發生在屬性配置完以后,在初始化回調之前.
Other Aware Interfaces
| 名稱 | 注入的依賴 | 詳細解釋 |
| ApplicationContextAware | 聲明的ApplicationContext. | ApplicationContextAware and BeanNameAware |
| ApplicationEventPublisherAware | ApplicationContext中的事件發布器. | Additional Capabilities of the ApplicationContext |
| BeanClassLoaderAware | 加載bean的類加載器 | Instantiating Beans |
| BeanFactoryAware | 聲明的BeanFactory. | ApplicationContextAware and BeanNameAware |
| BeanNameAware | bean的Name | ApplicationContextAware and BeanNameAware |
| BootstrapContextAware | 容器運行的資源適配器BootstrapContext 僅JCA環境下有效 | JCA CCI |
| LoadTimeWeaverAware | 加載期間處理類的weaver | Load-time Weaving with AspectJ in the Spring Framework |
| MessageSourceAware | 解析消息的配置策略 | Additional Capabilities of the ApplicationContext |
| NotificationPublisherAware | Spring JMX 通知發布器 | Notifications |
| ResourceLoaderAware | 配置的資源加載器 | Resources |
| ServletConfigAware | 容器當前運行的 ServletConfig,僅在web下的Spring ApplicationContext有效. | Spring MVC |
| ServletContextAware | 容器當前運行的 ServletContext,僅在web下的Spring ApplicationContext有效. | Spring MVC |
Bean定義繼承
如果編程式地使用ApplicationContext接口,子bean 的定義可以通過ChildBeanDefinition 類來表示。更常用在ClassPathXmlApplicationContext中聲明式地配置bean的定義。
<bean id="inheritedTestBean" abstract="true"
??????? class="org.springframework.beans.TestBean">
??? <property name="name" value="parent"/>
??? <property name="age" value="1"/>
</bean>
<bean id="inheritsWithDifferentClass"
??????? class="org.springframework.beans.DerivedTestBean"
??????? parent="inheritedTestBean" init-method="initialize"> ?
??? <property name="name" value="override"/>
??? <!-- the age property value of 1 will be inherited from parent -->
</bean>
子bean如果沒有指定class ,它將使用父bean定義的class ,也可以進行重載。
子bean定義可以從父類繼承:
- scope
- constructor argument values
- property values
- method overrides from the parent
- add new values
會覆蓋父bean的包括:
- scope
- initialization method
- destroy method
- static factory method
總是使用子bean的:
- depends on
- autowire mode
- dependency check
- singleton
- lazy init
父bean定義沒有明確地指出所屬的類,那么標記父bean定義為abstract 是必需的
<bean id="inheritedTestBeanWithoutClass" abstract="true">
??? <property name="name" value="parent"/>
??? <property name="age" value="1"/>
</bean>
嘗試獨立地使用這樣一個abstract的父bean,把它作為另一個bean引用,或者根據這個父bean的id顯式調用getBean()方法,將會返回一個錯誤。類似地,容器內部的preInstantiateSingletons()方法,也忽略定義為抽象的bean定義.
?
?
總結
以上是生活随笔為你收集整理的Spring--IoC(1)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: spring-tx
- 下一篇: Spring--IoC(2)