前言:
經過一系列被鄙視後,終於拿到一份還算滿意的小offer,以後的日子就是不斷學習的過程了,加油。
最近開始學習Spring框架,現將自己目前的學習狀況以及自己的一部分理解總結一下。
Spring框架提供給用戶的有兩個大的主要功能:控制反轉(IOC)或依賴注入(DI)和麪向切面編程(AOP),當然還提供了其他的比如操作數據庫的模板化以及事務管理、web開發中的Spring MVC等,這些以後會單獨總結,但是最主要的還是依賴注入和麪向切面編程。
一、Spring IOC以及DI的必要性
本篇文章總結控制反轉(IOC)和依賴注入(DI),兩者共同解決一個問題:對象耦合問題。下面舉例說明。
在我們訪問數據庫時會寫DAO,假設現在需要針對不同的數據庫(mysql或oracle數據庫)來寫DAO,於是會有一個DAO接口類,並且會有針對不同數據庫的相應的實現類(MysqlDaoImpl 和 OracleDaoImpl),如下所示:
DAO接口類:
package com.springframework.ioc;
public interface MyDao{
//添加
public void addUser();
}
mysql實現類:
package com.springframework.ioc;
public class MysqlDaoImpl implements MyDao{
@Override
public void addUser(){
System.out.println("mysql add user...");
}
}
oracle實現類:
package com.springframework.ioc;
public class OracleDaoImpl implements MyDao{
@Override
public void addUser(){
System.out.println("oracle add user...");
}
}
這時上層service層要調用DAO,假設我的寫法如下:
package com.springframework.ioc;
public class MyService{
private MyDao myDao;
public MyService(){
myDao = new MysqlDaoImpl();
}
public void addUser(){
myDao.addUser();
}
}
<constructor-arg ref = "oracle"/>
這種寫法的缺點就是當我要換用作oracle數據庫時需要修改MyService類,將myDao的創建改爲
myDao = new OracleDaoImpl();
不符合開放-封閉原則, 於是我修改我的代碼如下:
package com.springframework.ioc;
public class MyService{
private MyDao myDao;
public MyService(MyDao myDao){
this.myDao = myDao;
}
public void addUser(){
myDao.addUser();
}
}
這樣第三方在調用service層的addUser方法時的代碼如下:
package com.springframework.ioc;
public class Test{
public static void main(String[] args){
MyService myService = new MyService(new MysqlDaoImpl());
myService.addUser();
}
}
這樣當第三方需要的是oracle數據庫的操作時,只需要將自己本身的代碼修改爲:
MyService myService = new MyService(new OracleDaoImpl());
將myDao對象的具體創建控制權交給第三方,而另外兩房不需要關心,這就叫做控制反轉。
以上是傳統的編程做法,其實Spring框架也同樣爲我們解決了以上問題,使用的方式就是依賴注入方式。下面採用Spring框架來實現兩個對象之間的解耦。
在Spring框架中,每一個類對象叫做一個bean,每一個bean有一個bean id和具體的實現類,Spring的IOC容器就是對這些bean進行管理和配置,將bean與bean之間的依賴通過配置文件或者註解的方式表現出來。
還是以以上的例子爲例,爲在xml文件中配置bean並將bean和bean之間的依賴進行配置如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd" default-lazy-init="true"> <!-- beans declare go here --> <!-- 通過默認構造器注入 --> <bean id="mysql" class="com.springframework.ioc.MysqlDaoImpl"/> <bean id="oracle" class="com.springframework.ioc.OracleDaoImpl"/> <!-- 通過含參構造器注入 --> <bean id = "myservice" class="com.springframework.ioc.MyService"> <constructor-arg ref = "mysql"/> </bean> </beans>
這樣就將每個類作爲bean注入到spring 的IOC容器中,然後第三方就可以獲取bean並實例化bean,並調用其方法。如下所示:
package com.springframework.ioc;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test{
public static void main(String[] args){
//傳統做法
/*MyService myService = new MyService(new OracleDaoImpl());
myService.addUser(); */
//使用spring後的做法
ApplicationContext context = new ClassPathXmlApplicationContext("ioc.xml");
MyService myService = (MyService)context.getBean("myservice");
myService.addUser();
}
}
第三方如果想要調用的是oracle,則只需修改xml配置文件將
<constructor-arg ref = "mysql"/>
修改爲:
<constructor-arg ref = "oracle"/>
即可。
將一個類通過xml配置文件的形式注入到另一個類中,實現了兩個類之間的依賴,並且實現瞭解耦。兩個類只需要關注自己的業務邏輯實現,無需管理其它類的實現。
二、Spring IOC&DI的實現原理
Spring管理bean和bean之間的依賴管理分別對應於對象的控制反轉和依賴注入,控制反轉即spring將xml文件進行解析,包括bean的id、class名字、別名、property屬性等,解析爲一個個的bean註冊到IOC容器中;依賴注入即將bean通過反射的方式實例化bean,並通過三種注入方式將其他的bean注入進來,然後返回給第三方。
比如對於一個簡單配置的spring 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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<!-- beans declare go here -->
<!-- 通過默認構造器注入 -->
<bean id="mysql" class="com.springframework.ioc.MysqlDaoImpl"/>
<bean id="oracle" class="com.springframework.ioc.OracleDaoImpl"/>
</beans>
當Spring框架啓動時,會對xml文件進行解析,按照節點首先解析的是
<bean id="mysql" class="com.springframework.ioc.MysqlDaoImpl"/>
在Spring框架中維護一個BeanDefinition來存儲每一個bean節點的屬性,如id、classname、propertity、autowire mode、initmethod name 、destroymethod name 等。
如下所示:
class BeanDefinition{
//bean 的id
private String id;
//bean 的classname
private String className;
//bean 的init method name
private String initMethodName;
//等等...
}
每有一個BeanDefinition即有一個相應的對象,內部裝載着以上介紹的bean的各種屬性(id、className等),然後將這些BeanDefinition對象裝載到IOC容器中,內部爲鍵值爲nbeanName,值爲BeanDefinition的map表,如下所示:
/** Map of bean definition objects, keyed by bean name */
private final Map<String, BeanDefinition> beanDefinitionMap =
new ConcurrentHashMap<String, BeanDefinition>(64);
這就完成了對xml文件的解析以及對BeanDefinition實例的註冊。
當調用
MyService myService = (MyService)context.getBean("myservice");
時,
即爲從map表中根據鍵值beanname(即bean的id)取出BeanDefinition實例,然後通過工廠方式或者構造函數反射機制創建bean實例,然後對beandefinition中的property屬性進行解析,並通過set方法或者默認構造函數實現其他bean實例的注入。
這樣就將每一個類以及類與類之間的依賴以bean的形式交給Spring的IOC容器來管理。
詳細的IOC&DI的源代碼解析或者原理解析可以參考這篇文章 http://www.cnblogs.com/ITtangtang/p/3978349.html。
參考:
http://www.importnew.com/13619.html
http://www.cnblogs.com/xdp-gacl/p/4249939.html
www.cnblogs.com/ITtangtang/p/3978349.html