Spring系列__02IOC模塊簡介
Spring的兩大核心功能就是IoC和AOP,這篇文章主要介紹IoC。
簡單來說,在面向對象思想下,A類中有一個B類的屬性, 那么我們在創建A類時往往需要同時創建一個B類的對象,以便A類對其進行調用。但是,這樣的后果便是,A類和B類的耦合度過高。而所謂的IoC(控制反轉),其核心是DI,旨在提供一種更簡單的機制來設置組件依賴項(通常稱為對象的協作者),并在整個生命周期中管理這些依賴項。IoC通常可以分為兩種子類型:依賴注入和依賴查找。
依賴注入和依賴查找的介紹
依賴查找是一種更傳統的方法,有兩種類型:
- 依賴拉取(dependency pull,DL)
在依賴拉取中,根據需要從注冊表中提取依賴項,在IoC中,往往是使用JNDI查找來獲取依賴項。 - 上下文依賴查找(contextualized dependency lookup, ontextualized dependency lookup, CDL)
上下文查找和依賴拉取有些相似,但是不同之處就是,查找是針對管理資源的容器執行的,而不是來自某個中央注冊表。
當容器準備將依賴傳遞給組件時,會依次調用每個組件的performLookup()方法,然后,組件可以使用Contaioner接口來查找所需要的依賴項。
依賴注入是目前被廣泛采用的新方案,也有兩種常見的風格
- 構造函數
當在組件的構造函數中提供依賴項的時候,就會發生構造函數依賴注入。
首先,組件聲明一個或者一組構造函數,并將其作為依賴項作為參數;
然后在組件實例化的時候有IoC容器將依賴項傳遞給組件。如下代碼所演示:
public class ConstuctorInjecttion {
private Dependency dependency;
public ConstructorInjection(Dependency dependency) {
this.dependency = dependency;
}
}
構造函數注入屬于強制性的依賴注入,如果沒有依賴項的時候,就不能創建對象;因此,必須有依賴項。
- setter方法注入
在setter依賴注入中,IoC容器通過使用JavaBean樣式的setter方法注入組件的依賴項。組件的setter方法公開了IOC容器可以管理的依賴項。示例如下:
public class SetterInjection {
private Dependency dependency;
public void setDependency(Dependency dependency) {
this.dependency = dependency;
}
}
Setter注入屬于可選依賴注入,可以在沒有依賴項的情況下創建對象(調用默認的無參構造器), 然后通過調用setter方法來提供依賴項。
查找和注入的比較總結
- 查找:
1.依賴拉取代碼必須主動獲得對注冊表的依賴選項;
2.難以獨立于容器進行測試。
- 注入:
1.對代碼沒有任何影響;
2.可以獨立于容器進行測試。
3.開發更加自由。
setter注入和構造器注入比較總結
- 構造器注入:當對依賴項的依賴關系為強制依賴的時候,推薦使用這種方式,通過調用對應的帶參數構造器來完成依賴的注入。
- setter方法注入:最簡單的注入方式。當對依賴項的依賴關系為可選的時候,推薦使用這種方式。通過反射調用無參構造器來實例后,再調用對應的setter方法。這種方式的最大的好處就是:侵入性最小。
Spring中的控制反轉
對于控制反轉,Spring的實現核心是基于依賴注入,而Spring的依賴注入的核心是BeanFactory接口。BeanFactory負責管理組件,包括依賴項以及他們的生命周期。但是,在實際的使用中,Beanfactory的使用并不是很多,通常使用其擴展:ApplicationContext,其是BeanFactory的子接口。后面會有詳細介紹。
1.bean的配置
1.配置形式:
有三種配置形式,但是通常說兩種:xml文件和注解,(有些書籍上說第三種是自動裝配)。本文中主要介紹xml形式進行bean的配置。
2.配置方式:
bean的配置方式有三種:
- 通過反射的方式進行配置:
該方式會通過反射機制調用無參構造器來創建對象實例,但是,若是沒有無參構造器,則會拋出異常。
<!--
bean的配置方式1:通過全類名方式(反射)
id:IOC容器中唯一存在,若不指定會默認使用全類名
-->
<bean id="car" class="com.spring.demo.bean.Car">
<!--
- 通過工廠方法(靜態工廠、實例工廠):
該方法會運用了工廠模式的思想,通過調用靜態工廠、實例工廠的方法來創建bean的實例。
<!--
bean的配置方式2:通過靜態工廠方法
id 屬性:指定 bean 的 id,用于從容器中獲取
class 屬性:指定靜態工廠的全限定類名
factory-method 屬性:指定生產對象的靜態方法
-->
<bean id="carByFactory" class="com.spring.demo.bean.StaticFactory"
factory-method="getCarBean">
</bean>
<!--
bean的配置方式2:通過實例工廠方法
此種方式是:先把工廠的創建交給 spring 來管理,然后在使用工廠的 bean 來調用里面的方法
factory-bean 屬性:用于指定實例工廠 bean 的 id。
factory-method 屬性:用于指定實例工廠中創建對象的方法。
-->
<bean id="instanceFactory" class="com.spring.demo.bean.InstanceFactory"></bean>
<bean id="carByInstance" class="com.spring.demo.bean.Car" factory-bean="instanceFactory"
factory-method="getCarByInstance">
<property name="brand" value="大眾"/>
</bean>
- 通過Spring提供的FactoryBean進行配置(使用較少,暫不介紹)。
3.依賴注入的方式:屬性注入、構造器注入
依賴注入: Dependency Injection。 它是 spring 框架核心 ioc 的具體實現。我們的程序在編寫時, 通過控制反轉, 把對象的創建交給了 spring,但是代碼中不可能出現沒有依賴的情況。IoC 解耦只是降低他們的依賴關系,但不會消除。 例如:我們的業務層仍會調用持久層的方法。那這種業務層和持久層的依賴關系, 在使用 Spring 之后, 就讓 Spring 來維護了。簡單的說,就是坐等框架把持久層對象傳入業務層,而不用我們自己去獲取。
依賴注入的方式主要有兩種:
- 屬性注入就是調用setter方法進行屬性注入,;
- 構造器注入方式是調用重載的帶參構造器。
說到這里,可以來一些代碼了:
<!--
依賴注入方式1:屬性注入,屬性注入是通過setter方法注入bean的屬性值或者依賴的對象。
也是實際應用中最常用的方式。
name指定屬性值,value或者value節點指定屬性值,ref指定依賴的對象
-->
<property name="company" value="大眾"/>
<property name="brand" value="寶來"/>
<property name="price" value="200000.00"/>
<property name="maxSpeed" value="200"/>
</bean>
<!--
依賴注入方式2:構造器注入
通過構造方法注入屬性,能夠保證bean在實例化后就能使用
沒有name屬性,默認按照構造器的變量順序加載,也可以通過index或者type進行限制
index:指定參數在構造函數參數列表的索引位置
type:指定參數在構造函數中的數據類型
-->
<!--
若一個 bean 有多個構造器, 如何通過構造器來為 bean 的屬性賦值
可以根據 index 和 value 進行更加精確的定位. (了解)
也可以通過name來具體制定屬性名進行綁定
若字面值中包含特殊字符, 則可以使用 DCDATA 來進行賦值. (了解)
使用構造器方式注入屬性值時,可以使用type或index來區別重載構造器
-->
<bean id="car1" class="com.spring.demo.bean.Car">
<constructor-arg value="寶馬" type="java.lang.String"/>
<constructor-arg value="mini" type="java.lang.String"/>
<constructor-arg value="200000.00" type="double"/>
<constructor-arg value="240" type="int"/>
</bean>
<!--
依賴注入方式3:工廠方法注入
因為這種方法比較少使用,這里只是寫一下注釋,沒有demo
-->
4.IOC容器BeanFactory和ApplicationContext簡介
BeanFactory是Spirng框架的基礎設施,面向Spring本身;ApplicationContext是BeanFactory的子接口,面向Spring框架的應用者,其提供了更多的功能。
ApplicationContext具有兩個只要的實現類:ClasspathXmlApplicationContext和FileSystemXmlApplicationContext;前者通過在類路徑下加載配置文件來完成bean的裝配,后者從文件系統下加載配置文件。項目結構如下所示:
 {
ApplicationContext context = new ClassPathXmlApplicationContext("bean-ioc.xml");
Car car = (Car) context.getBean("car");
System.out.println(car);
}
5.字面值
字面值可以用value標簽或者value屬性進行注入。基本類型及其包裝類、String等都是采用字面值的方式進行注入。當具有特殊字符的時候,需要特殊處理:
<!--
value簡介:
基本類型及其包裝類、String都可以通過value標簽或者value屬性注入,
當字面值包含特殊字符的時候,可用<![CDATA[]]>包含
-->
<bean id="car2" class="com.spring.demo.bean.Car">
<property name="company" value="大眾"/>
<property name="brand">
<value><![CDATA[<DZ寶來>]]></value>
</property>
<property name="maxSpeed" value="200"/>
<property name="price" value="200000.00"/>
</bean>
spring中某一屬性不進行顯式聲明時,其會采用默認值,當然你可以顯式的注入null:適應null標簽
同時,spring支持級聯屬性的注入。
6.引用其他bean
當需要引用其他的bean時(A類引用B類),可以通過ref標簽或者ref屬性進行注入。其用法和value類似。泣別:value注入的是字面值,ref注入的是一個引用。當然,內部bean的注入也是這種方式。
示例如下:
<!--
引入其他bean:
例如person有car屬性,需要引入,這是就不是使用value屬性了,
而是使用ref指定需要引入的bean
也可以聲明一個內部bean,此時內部bean就無需再指定id了,道理和內部類相似
-->
<bean id="person" class="com.spring.demo.bean.Person">
<property name="name" value="麗麗"/>
<property name="age" value="23"/>
<property name="car" ref="car"/>
</bean>
<!-- innerBean -->
<bean id="person1" class="com.spring.demo.bean.Person">
<property name="name" value="麗麗"/>
<property name="age" value="23"/>
<property name="car">
<bean class="com.spring.demo.bean.Car">
<property name="brand" value="BMW"/>
<property name="company" value="BMW"/>
<property name="maxSpeed" value="233"/>
<property name="price" value="233333.33"/>
</bean>
</property>
</bean>

7.集合屬性
在Spring中可以通過xml標簽配置集合屬性:
1.list和數組
配置java.util.LIst和數組類型時,可以通過標簽來配置,list中的屬性可以使用value標簽或者ref標簽。
可以使用utility scheme來單獨定義集合,這樣的好處就是方便其他bean進行引用。
2.set
set的情況和list相似,使用set標簽,用法和list標簽一樣。
<!--
集合屬性:
list標簽:數組和list數據使用這個標簽,
set標簽:set數據使用,使用方法同list標簽
-->
<bean id="stu" class="com.spring.demo.bean.Student">
<property name="name" value="蘇大強"/>
<property name="age" value="60"/>
<property name="cars">
<list>
<ref bean="car"/>
<ref bean="car1"/>
<ref bean="car2"/>
<ref bean="car3"/>
</list>
</property>
</bean>
<!--
也可以單獨定義list,然后直接綁定
-->
<util:list id="carList">
<ref bean="car"/>
<ref bean="car1"/>
<ref bean="car2"/>
<ref bean="car3"/>
</util:list>
<bean id="stu1" class="com.spring.demo.bean.Student">
<property name="name" value="蘇大強"/>
<property name="age" value="60"/>
<property name="cars" ref="carList"/>
</bean>
3.map和properties
java.util.Map可以通過map標簽進行指定,map標簽中有多個entity子標簽,每個entity定義一對鍵值對。
在entity中可以通過key屬性或者key標簽來指定鍵,同理可以使用value、ref、bean、null屬性或者標簽指定值。
<!--
map:
map的使用可list相似,使用entry標簽來保存每一對鍵值對,
同時可以使用key屬性或者key標簽來存儲key,
value值或value屬性綁定普通的字面值,ref綁定其他bean作為值
-->
<bean id="teacher" class="com.spring.demo.bean.Teacher">
<property name="name" value="張莉"/>
<property name="age" value="24"/>
<property name="carMap">
<map>
<entry key="car" value-ref="car"/>
<entry key="car1" value-ref="car1"/>
<entry key="car2" value-ref="car2"/>
<entry key="car3" value-ref="car3"/>
</map>
</property>
</bean>
props標簽用來定義java.util.Properties,該標簽有子標簽prop,具體使用方法與map一樣。
8.p命名空間
Spring2.5之后支持p命名空間,可以簡化xml配置,屬性與原來的配置一樣:
<!--
spring2.5之后,可以使用p命名空間來簡化屬性配置,需要在配置文件中引入p命名空間
xmlns:p="http://www.springframework.org/schema/p"
-->
<bean id="car4" class="com.spring.demo.bean.Car" p:brand="寶馬" p:company="寶馬"
p:price="2000000.00" p:maxSpeed="200"/>
8.Java配置
前面的例子都是通過xml文件進行bean的配置,現在來示范一下通過Java配置方式來配置。
@Configuration //該注解表示這個類是一個配置類,其作用相當于配置文件。
public class CarConfigure {
@Bean //該注解表示會向容器中配置一個bean,其value屬性是設置bean的id,
//沒有的話默認使用方法名作為id,bean的類型就是返回值類型
public Car carByAnnotation() {
return new Car();
}
}
9.autowire
Spring的IOC容器可以通過自動裝配來配置bean,使用方式就是使用autowire屬性,有兩種模式:byName和byType。
byName會在Spring容器中根據名字取尋找匹配的bean,沒有的話就無法完成裝配;
byType會在Spring容器中按照類型來查找并進行bean的配置,但是當找到多個符合條件的類型的bean時會報異常,不過可以通過primary屬性或注解來制定bean的優先級,但是當你設置了多個同一類型的bean的primary屬性為true的時候失效;這個時候,可以借助@Qualifier注解來加以限制。
<!-- 自動裝配: 只聲明 bean, 而把 bean 之間的關系交給 IOC 容器來完成 -->
<!--
byType: 根據類型進行自動裝配. 但要求 IOC 容器中只有一個類型對應的 bean, 若有多個則無法完成自動裝配.
byName: 若屬性名和某一個 bean 的 id 名一致, 即可完成自動裝配. 若沒有 id 一致的, 則無法完成自動裝配
-->
<!-- 在使用 XML 配置時, 自動轉配用的不多. 但在基于 注解 的配置時, 自動裝配使用的較多. -->
<bean id="car" class="com.spring.demo.bean.Car">
<property name="company" value="大眾"/>
<property name="brand" value="寶來"/>
<property name="price" value="200000.00"/>
<property name="maxSpeed" value="200"/>
</bean>
<bean id="person" class="com.spring.demo.bean.Person" autowire="byName">
<property name="name" value="James"/>
<property name="age" value="23"/>
<property name="car" ref="carSub"/>
</bean>
<!-- <bean id="person1" class="com.spring.demo.bean.Person" autowire="byType">
<property name="name" value="James"/>
<property name="age" value="23"/>
</bean>-->
<!--
自動裝配的缺點:
1.autowire默認會為bean裝配全部屬性,所以當你只想裝配部分屬性時顯得不夠靈活;
2.byName和byType兩種模式只能選擇一個,不能兼有。
-->
@Test
public void testByName() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean-autowire.xml");
Person person = (Person) context.getBean("person");
System.out.println(person);
}
@Test
public void testByType() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean-autowire.xml");
Person person = (Person) context.getBean("person1");
System.out.println(person);
}
10.bean的繼承與依賴
作為一個Java程序員,肯定會接觸到繼承。Spring中也提供了bean的繼承,思想和面向對象的繼承的思想相似。區別在于Spring中的bean的繼承是指bean的配置可以被繼承(復用)。
<!--
bean的繼承:
你可以定義一個bean并有其他bean繼承,這樣做的好處是能減少配置
父bean的配置會由子bean繼承,當然部分屬性除外(autowire,abstract)
子bean可以重新賦值一些屬性,進行bean重新定義
父bean可以定義為abstract,定義為abstract的bean只能被繼承,不能被注冊(實例化)
父bean可以不定義class屬性,由子類自己定義
子bean需要通過parent屬性指定你繼承的父bean
-->
<bean id="carSup" class="com.spring.demo.bean.Car" p:company="寶馬" p:brand="mini"
p:maxSpeed="270" p:price="300000.00" abstract="true"/>
<bean id="carSub" parent="carSup"/>
@Test
public void testInherit() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean-autowire.xml");
Car car = (Car) context.getBean("carSub");
System.out.println(car);
}
在本文的開頭,我們引入了A類和B類,當你想創建A類時,而A類又必須有B類的變量時,便可以使用依賴來解決這件事:
<!--
bean的依賴:
你可以聲明當前bean依賴于哪些bean,這樣當前bean初始化之前會去初始化依賴的bean,
如果沒有注冊導致無法初始化便會報錯
依賴多個bean的時候,可以使用逗號或者空格分開
-->
<bean id="person1" parent="person" depends-on="carSub"/>
@Test
public void testDepends() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean-autowire.xml");
Person person1 = (Person) context.getBean("person1");
System.out.println(person1);
}
9.bean的作用域
Spring中的bean的作用域有以下幾種:
singleton, property, request,session
默認情況下,spring中每個bean只有一個實例,但是可以顯示指定。
<!--
bean的生命周期:
singleton:默認選項,IOC容器默認為每個bean只實例化一個實例,該實例在調用具體的bean之前完成。
property:原型。該方式下IOC容器會在每次調用bean的時候實例化一個新的實例,并且只有在調用時進行bean的初始化
request和session同JavaWeb中一樣,在此不多加介紹
-->
<bean id="dog" class="com.spring.demo.bean.Dog" p:name="haki" scope="prototype"/>
package com.spring.demo.bean;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@AllArgsConstructor
@ToString
public class Dog {
private String name;
public Dog() {
super();
System.out.println("dog's constructor");
}
}
@Test
public void testScope() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean-autowire.xml");
Dog dog = (Dog) context.getBean("dog");
Dog dog1 = (Dog) context.getBean("dog");
System.out.println(dog == dog1);
}
在上面的demo中,除了可以查看bean的創建個數,還應該查看bean的創建時機。

浙公網安備 33010602011771號