概述
上一篇spring概述我們搭建完基于 Spring 框架的環(huán)境, 這篇我們開始真正的閱讀 Spring 的源碼,分析 Spring 的源碼之前我們先來簡單回顧下 Spring 核心功能的簡單使用。
為什么需要 IoC
假如有這么一個業(yè)務場景:dao 層從不同的地方獲取用戶數據,service 層用來調用獲取用戶的方法,如何控制從想要的地方獲取用戶數據?
1、先寫一個 User 類
public class User { private String name; public User() { } public User(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "User{" + "name='" + name + '\'' + '}'; }}
2、寫一個 UserDao 接口
public interface UserDao { public void getUser();}
3、再去寫 Dao 的實現(xiàn)類
public class UserDaoImpl implements UserDao { public void getUser() { User user = new User("hresh"); System.out.println("從bean中獲取到的用戶數據為"+user); }}
4、寫 UserService 的接口
public interface UserService { public void getUser();}
5、最后寫 UserService 的實現(xiàn)類
public class UserServiceImpl implements UserService { private UserDao userDao = new UserDaoImpl(); public void getUser() { userDao.getUser(); }}
6、測試一下
public class UserGetTest { @Test public void getUser(){ UserService userService = new UserServiceImpl(); userService.getUser(); }}
這樣就實現(xiàn)了一種讀取用戶信息的方式,接下來我們再增加一種從 Mysql 數據庫中讀取用戶信息的方法。
再增加 UserDao 的實現(xiàn)類
public class UserDaoMysqlImpl implements UserDao { public void getUser() { User user = new User("acorn"); System.out.println("從MySQL數據庫中獲取到的用戶數據為"+user); }}
緊接著我們要去使用 MySql 的話 , 我們就需要去 service 實現(xiàn)類里面修改對應的實現(xiàn)。
public class UserServiceImpl implements UserService { private UserDao userDao = new UserDaoMySqlImpl(); @Override public void getUser() { userDao.getUser(); }}
同樣如果我們需要從 Oracle 數據庫中讀取數據,還需要構建一個 UserDao 的實現(xiàn)類,然后修改 UserServiceImpl 類。 假設我們的這種需求非常大 , 這種方式就根本不適用了,每次變動 , 都需要修改大量代碼 . 這種設計的耦合性太高了, 牽一發(fā)而動全身 。
那我們如何去解決?
我們可以在調用 UserDao 實現(xiàn)類的地方,不去實例化該對象,而是留出一個接口 ,利用 set 方法,代碼如下:
public class UserServiceImpl implements UserService { private UserDao userDao; // 利用set實現(xiàn) public void setUserDao(UserDao userDao) { this.userDao = userDao; } @Override public void getUser() { userDao.getUser(); }}
現(xiàn)在在測試類里,進行測試:
public class UserGetTest { @Test public void getUser(){ UserServiceImpl userService = new UserServiceImpl(); userService.setUserDao(new UserDaoImpl()); userService.getUser(); userService.setUserDao(new UserDaoMysqlImpl()); userService.getUser(); }}
執(zhí)行結果為:
從bean中獲取到的用戶數據為User{name='hresh'}從MySQL數據庫中獲取到的用戶數據為User{name='acorn'}
雖然只是 UserServiceImpl 類中的代碼做了修改,看起來變動不大,甚至你可能會說測試類中還變復雜了。但是仔細想一下,之前所有的 Dao 實現(xiàn)類都是在 UserServiceImpl 中控制創(chuàng)建,而現(xiàn)在由更接近用戶的測試類中控制創(chuàng)建對象,把主動權交給了調用者,程序不用去管怎么創(chuàng)建,怎么實現(xiàn)了,它只負責提供一個接口即可。
這種思想 ,從本質上解決了問題 , 我們程序員不再去管理對象的創(chuàng)建了,更多的去關注業(yè)務的實現(xiàn) ,耦合性大大降低 。這也就是 IoC 的原型 !
IoC本質
IoC( Inverse of Control:控制反轉 )是一種設計思想,就是將原本在程序中手動創(chuàng)建對象的控制權,交由 Spring 框架來管理。IoC 在其他語言中也有應用,并非 Spring 特有。IoC 容器是 Spring 用來實現(xiàn) IoC 的載體,IoC 容器實際上就是個 Map(key,value),Map 中存放的是各種對象。
要了解控制反轉,有必要先了解軟件設計的一個重要思想:依賴倒置原則( Dependency Inversion Principle )。
- 高層模塊不應該依賴于底層模塊,兩者應該依賴于其抽象。
- 抽象不應該依賴具體實現(xiàn),具體實現(xiàn)應該依賴抽象。
上面2點是依賴倒置原則的概念,也是核心。主要是說模塊之間不要依賴具體實現(xiàn),依賴接口或抽象。
其實依賴倒置原則的核心思想是面向接口編程。

將對象之間的相互依賴關系交給 IoC 容器來管理,并由 IoC 容器完成對象的注入。這樣可以很大程度上簡化應用的開發(fā),把應用從復雜的依賴關系中解放出來。IoC 容器就像是一個工廠一樣,當我們需要創(chuàng)建一個對象的時候,只需要配置好配置文件/注解即可,完全不用考慮對象是如何被創(chuàng)建出來的。在實際項目中一個 Service 類可能有幾百甚至上千個類作為它的底層,假如我們需要實例化這個 Service,你可能要每次都要搞清楚這個 Service 所有底層類的構造函數,這可能會把人逼瘋。如果利用 IoC 的話,你只需要配置好,然后在需要的地方引用就行了,這大大增加了項目的可維護性且降低了開發(fā)難度。
IoC 在 Spring 中有多種實現(xiàn)方式,可以使用 XML 配置,也可以使用注解,新版本的 Spring 也可以零配置實現(xiàn) IoC。
Spring 容器在初始化時先讀取配置文件,根據配置文件或元數據創(chuàng)建與組織對象存入容器中,程序使用時再從 IoC 容器中取出需要的對象。

采用 XML 方式配置 Bean 的時候,Bean 的定義信息是和實現(xiàn)分離的,而采用注解的方式可以把兩者合為一體,Bean 的定義信息直接以注解的形式定義在實現(xiàn)類中,從而達到了零配置的目的。
實戰(zhàn)分析
編寫代碼
定義一個 bean 類:
public class User { private String name; public User() { } public User(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "User{" + "name='" + name + '\'' + '}'; }}
源碼很簡單,bean 沒有特別之處,Spring 的目的就是讓我們的 bean 成為一個純粹的 POJO,這就是 Spring 追求的,接下來就是在配置文件中定義這個 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/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="user" class="com.msdn.bean.User"> <property name="name" value="hresh" /> </bean></beans>復制代碼
在上面的配置中我們可以看到bean的聲明方式,在spring中的bean定義有N種屬性,但是我們只要像上面這樣簡單的聲明就可以使用了。
具體測試代碼如下:
public class MyBeanTest { @Test public void MyBean(){ //解析application_context.xml文件 , 生成管理相應的Bean對象 ApplicationContext context = new ClassPathXmlApplicationContext("application_context.xml"); //getBean : 參數即為spring配置文件中bean的id . User user = (User) context.getBean("user"); System.out.println(user); }}
執(zhí)行結果為:
User{name='hresh'}
思考
User 對象是誰創(chuàng)建的?【user 對象是由 Spring 創(chuàng)建的】
-
User 對象的屬性是怎么設置的?【user 對象的屬性是由 Spring 容器設置的】
這個過程就叫做控制反轉:
控制:誰來控制對象的創(chuàng)建,傳統(tǒng)應用程序的對象是由程序本身控制創(chuàng)建的,使用 Spring 后,對象是由 Spring 來創(chuàng)建的。
-
反轉:程序本身不創(chuàng)建對象,而變成被動地接收對象。
依賴注入:利用 set 方法來進行注入的。
IOC是一種編程思想,由主動的編程變成被動的接收 。
關于 ClassPathXmlApplicationContext 的學習后續(xù)會單獨介紹,有興趣的朋友可以去看一下。
按照上述的方式我們對之前提到的業(yè)務場景進行修改。首先新增 一個 Spring 配置文件 application_context.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/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="mysqlImpl" class="com.msdn.dao.UserDaoMysqlImpl" /> <bean id="oracleImpl" class="com.msdn.dao.UserDaoOracleImpl" /> <bean id="serviceImpl" class="com.msdn.service.UserServiceImpl"> <!--注意: 這里的name并不是屬性 , 而是set方法后面的那部分 , 首字母小寫--> <!--引用另外一個bean , 不是用value 而是用 ref--> <property name="userDao" ref="oracleImpl" /> </bean></beans>復制代碼
測試代碼如下:
@Testpublic void MyBean(){ ApplicationContext context = new ClassPathXmlApplicationContext("application_context.xml"); UserServiceImpl serviceImpl = (UserServiceImpl) context.getBean("serviceImpl"); serviceImpl.getUser();}
之后我們不需要再去程序中改動了,要實現(xiàn)不同的操作,只需要在 XML 配置文件中進行修改。所謂的 IoC 就是對象由 Spring 來創(chuàng)建、管理和裝配。
IoC涉及到的組件
在上文測試代碼中我們用到的是 ApplicationContext,具體實現(xiàn)是 ClassPathXmlApplicationContext。所以接下來我們簡單分析一下在此過程中涉及到的組件。
首先是 ClassPathXmlApplicationContext 類的繼承關系圖。

基本上包含了 IOC 體系中大部分的核心類和接口。 下面我們就針對這個圖進行簡單的拆分和補充說明。
Resource 主要負責對資源的抽象,它的每一個實現(xiàn)類都代表了一種資源的訪問策略,如 ClasspathResource 、 URLResource ,F(xiàn)ileSystemResource 等。

有了資源,就需要有資源加載模塊,Spring 利用 ResourceLoader 來進行統(tǒng)一資源加載,關系圖如下:

資源加載完畢之后就需要 BeanFactory 來進行加載解析,它是一個 bean 容器,其中 BeanDefinition 是它的基本結構,它內部維護著一 個 BeanDefinition map ,并可根據 BeanDefinition 的描述進行 bean 的創(chuàng)建和管理。

BeanFacoty 有三個直接子類 ListableBeanFactory、HierarchicalBeanFactory 和 AutowireCapableBeanFactory,DefaultListableBeanFactory 為最終默認實現(xiàn),它實現(xiàn)了所有接口。
BeanDefinition 用來描述 Spring 中的 Bean 對象。

BeanDefinitionReader 的作用是讀取 Spring 配置文件中的內容,將其轉換為 IoC 容器內部的數據結構:BeanDefinition。

ApplicationContext 是個 Spring 容器,也叫做應用上下文。它繼承 BeanFactory,同時也是 BeanFactory 的擴展升級版。由于 ApplicationContext 的結構就決定了它與 BeanFactory 的不同,其主要區(qū)別有:
- 繼承 MessageSource ,提供國際化的標準訪問策略;
- 繼承 ApplicationEventPublisher,提供強大的事件機制;
- 擴展 ResourceLoader,可以用來加載多個 Resource,可以靈活訪問不同的資源;
- 對 Web 應用的支持。


上述提到的六個重要知識點是 Spring IoC 中最核心的部分,后續(xù)的學習也是針對這些內容進行詳細解讀。
IoC創(chuàng)建對象
無參構造器
當對象由無參構造器創(chuàng)建時,屬性是由該類的 set 方法寫入的。
User 類
public class User { private String name; public User() { System.out.println("user無參構造方法"); } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "User{" + "name='" + name + '\'' + '}'; }}
application_context.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/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="user" class="com.msdn.bean.User"> <property name="name" value="hresh" /> </bean></beans>
測試代碼:
public class MyBeanTest { @Test public void MyBean(){ //解析application_context.xml文件 , 生成管理相應的Bean對象 ApplicationContext context = new ClassPathXmlApplicationContext("application_context.xml"); //在執(zhí)行getBean的時候, user已經創(chuàng)建好了,屬性是通過set方法寫入的 User user = (User) context.getBean("user"); System.out.println(user); }}
執(zhí)行結果為:
user無參構造方法User{name='hresh'}
如果將 User 類中的 set 方法注釋掉,再次調用測試代碼,會報錯,說明對象是由無參構造器創(chuàng)建成功后,會調用 set 方法完成實例的初始化。
有參構造器
User 類
public class User { private String name; public User() { System.out.println("user無參構造方法"); } public User(String name) { this.name = name; System.out.println("user有參構造方法"); } public String getName() { return name; }// public void setName(String name) {// this.name = name;// } @Override public String toString() { return "User{" + "name='" + name + '\'' + '}'; }}
application_context.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/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="user" class="com.msdn.bean.User"> <constructor-arg name="name" value="hresh" /> </bean></beans>
測試代碼:
public class MyBeanTest { @Test public void MyBean(){ //解析application_context.xml文件 , 生成管理相應的Bean對象 ApplicationContext context = new ClassPathXmlApplicationContext("application_context.xml"); User user = (User) context.getBean("user"); System.out.println(user); }}
執(zhí)行結果為:
user有參構造方法User{name='hresh'}
結論:Spring 容器根據 XML 文件中的配置,調用 bean 類的有參構造器來創(chuàng)建對象。
Spring中XML配置
別名
alias 設置別名 , 為bean設置別名 , 可以設置多個別名 。
<!--設置別名:在獲取Bean的時候可以使用別名獲取--><alias name="userT" alias="userNew"/>
Bean的配置
<!--bean就是java對象,由Spring創(chuàng)建和管理--><!-- id 是bean的標識符,要唯一,如果沒有配置id,name就是默認標識符 如果配置id,又配置了name,那么name是別名 name可以設置多個別名,可以用逗號,分號,空格隔開 如果不配置id和name,可以根據applicationContext.getBean(.class)獲取對象; class是bean的全限定名=包名+類名--><bean id="hello" name="hello2 h2,h3;h4" class="com.msdn.bean.Hello"> <property name="name" value="Spring"/></bean>
import
團隊的合作通過import來實現(xiàn) ,當有多個關于 bean 定義的文件,最后可以集中在一個文件中。
<import resource="{path}/beans.xml"/>
參考: