前言
什麼是IOC?
IOC(Inversion of Control),即“控制反轉”,是一種代碼設計思想。
在傳統的程式開發中,如果在一個對象中要使用其他的對象,就必須自己手動new一個,而且在使用完之後還需要将對象進行手動銷毀,這樣對象始終會和其他的類藕合起來。
對于Spring架構來說,所謂IOC(控制反轉),就是由Spring來負責控制對象的生命周期和對象之間的關系。所有的類都會在Spring容器中進行登記,告訴Spring我是什麼東西,我需要什麼東西,然後Spring會在系統運作到适當的時候,把你要的東西主動給你,同時也把你交給其他需要你的東西。所有的類的建立、銷毀都由Spring來控制,也就是說控制對象生存周期的不再是引用它的對象,而是Spring容器。
IoC和DI
DI—Dependency Injection,即“依賴注入” : 元件之間依賴關系 由容器在運作期決定,形象的說,即
由容器動态的将某個依賴關系注入到元件之中 。
依賴注入的目的并非為軟體系統帶來更多功能,而是為了提升元件重用的頻率,并為系統搭建一個靈活、可擴充的平台。
IoC和DI 由什麼 關系 呢?其實它們 是同一個概念的不同角度描述,相對IoC 而言,“依賴注入”明确描述了“被注入對象依賴IoC容器配置依賴對象”。IOC主要針對于控制對象的所有權,而DI側重于描述對象的使用的權。
SpringIOC底層實作原理
IOC的底層原理:xml解析,工廠模式,反射
IOC 是基于 IOC 容器完成,IOC 容器底層就是對象工廠
Spring提供IOC容器實作兩種方式:
- BeanFactory:IOC容器基本實作,是Spring内部的使用接口,不提供開發人員使用 ,加載配置檔案的時候不會建立對象,擷取(使用)對象的時候才會建立對象
-
ApplicationContext:BeanFactory的子接口,提供了更多更強大的功能,一般由開發人員進行使用
*加載配置檔案的時候就會建立配置檔案的對象。
詳細步驟:
- 利用傳入的參數擷取xml檔案的流,并且利用dom4j解析成Document對象。
- 對于Document對象擷取根元素對象後對下面的标簽進行周遊,判斷是否有符合的beanId。
- 如果找到對應的beanId,相當于找到了一個Element元素,開始建立對象,先擷取class屬性,然後根據屬性值利用反射建立對象。
- 周遊标簽下的property标簽,并對屬性指派。注意,需要單獨處理int,float類型的屬性,因為在xml配置中這些屬性都是以字元串的形式來配置的,是以需要進行額外的處理。
- 如果屬性property标簽有ref屬性,說明某個屬性的值是一個對象,那麼根據beanId(ref屬性的值)去擷取ref對應的對象,再給屬性指派。
- 傳回建立的對象,如果沒有對應的beanId或者下沒有子标簽都會傳回null。
基于xml實作IOC容器
1.建立一個maven項目
引入pom依賴:
<!--用于解析xml -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.0</version>
</dependency>
2.建立一個控制對象
@Data
@ToString
public class User {
private String userId;
private String userName;
}
3.配置xml檔案
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="user1" class="zzuli.edu.cn.entity.User">
<property name="userId" value="0001"></property>
<property name="userName" value="張三"></property>
</bean>
<bean id="user2" class="zzuli.edu.cn.entity.User">
<property name="userId" value="0002"></property>
<property name="userName" value="李四"></property>
</bean>
</beans>
4.建立IOC容器類
public class ClassPathXmlApplicationContext {
//xml配置檔案的路徑
private String xmlPath;
public ClassPathXmlApplicationContext(String xmlPath) {
this.xmlPath = xmlPath;
}
/**
* 擷取Bean對象
*/
public Object getBean(String beanId) throws Exception {
// 1.讀取xml配置檔案
SAXReader saxReader = new SAXReader();
Document read = saxReader.read(this.getClass().getClassLoader().getResourceAsStream(xmlPath));
//擷取xml配置檔案的根節點對象(<beans></beans>)
Element rootElement = read.getRootElement();
//擷取根節點中所有的子節點對象,也就是所有bean對象(<bean></bean>)
List<Element> beanElements = rootElement.elements();
Object obj = null;
for (Element beanElement : beanElements) {
// 2.使用beanId查找bean配置,并擷取配置檔案中class的位址(為了與參數beanId區分開,我們命名為beanElementId)
String beanElementId = beanElement.attributeValue("id");
//如果不是目前的bean,則跳出本次循環
if (!beanId.equals(beanElementId)) {
continue;
}
// 擷取bean對應的Class位址
String beanClassPath = beanElement.attributeValue("class");
// 3.使用反射執行個體化對象
Class<?> cls = Class.forName(beanClassPath);
obj = cls.newInstance();
// 4.擷取屬性配置,使用反射技術進行指派
List<Element> fieldElements = beanElement.elements();
for (Element fieldElement : fieldElements) {
String name = fieldElement.attributeValue("name");
String value = fieldElement.attributeValue("value");
//使用反射api為私有屬性指派
Field declaredField = cls.getDeclaredField(name);
//忽略通路權限修飾符的安全檢查,又稱為暴力反射
declaredField.setAccessible(true);
declaredField.set(obj, value);
}
}
//傳回bean對象
return obj;
}
}
5.使用和測試
public class IOCDemo {
public static void main(String[] args) throws Exception {
//讀取User的XML配置檔案
ClassPathXmlApplicationContext appLication = new ClassPathXmlApplicationContext("user.xml");
//擷取User的Bean對象
Object bean = appLication.getBean("user1");
User user = (User) bean;
System.out.println(user);
//擷取User的Bean對象
bean = appLication.getBean("user2");
user = (User) bean;
System.out.println(user);
}
}