概述爲何須要 IoCIoC本質實戰分析編寫代碼思考IoC涉及到的組件IoC建立對象無參構造器有參構造器Spring中XML配置參考文獻php
上一篇spring概述咱們搭建完基於 Spring 框架的環境, 這篇咱們開始真正的閱讀 Spring 的源碼,分析 Spring 的源碼以前咱們先來簡單回顧下 Spring 核心功能的簡單使用。java
假若有這麼一個業務場景:dao 層從不一樣的地方獲取用戶數據,service 層用來調用獲取用戶的方法,如何控制從想要的地方獲取用戶數據?mysql
一、先寫一個 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 + '\'' +
'}';
}
}
複製代碼
二、寫一個 UserDao 接口web
public interface UserDao {
public void getUser();
}
複製代碼
三、再去寫 Dao 的實現類spring
public class UserDaoImpl implements UserDao {
public void getUser() {
User user = new User("hresh");
System.out.println("從bean中獲取到的用戶數據爲"+user);
}
}
複製代碼
四、寫 UserService 的接口sql
public interface UserService {
public void getUser();
}
複製代碼
五、最後寫 UserService 的實現類數據庫
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDaoImpl();
public void getUser() {
userDao.getUser();
}
}
複製代碼
六、測試一下編程
public class UserGetTest {
@Test
public void getUser(){
UserService userService = new UserServiceImpl();
userService.getUser();
}
}
複製代碼
這樣就實現了一種讀取用戶信息的方式,接下來咱們再增長一種從 Mysql 數據庫中讀取用戶信息的方法。bash
再增長 UserDao 的實現類
public class UserDaoMysqlImpl implements UserDao {
public void getUser() {
User user = new User("acorn");
System.out.println("從MySQL數據庫中獲取到的用戶數據爲"+user);
}
}
複製代碼
緊接着咱們要去使用 MySql 的話 , 咱們就須要去 service 實現類裏面修改對應的實現。
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDaoMySqlImpl();
@Override
public void getUser() {
userDao.getUser();
}
}
複製代碼
一樣若是咱們須要從 Oracle 數據庫中讀取數據,還須要構建一個 UserDao 的實現類,而後修改 UserServiceImpl 類。 假設咱們的這種需求很是大 , 這種方式就根本不適用了,每次變更 , 都須要修改大量代碼 . 這種設計的耦合性過高了, 牽一髮而動全身 。
那咱們如何去解決?
咱們能夠在調用 UserDao 實現類的地方,不去實例化該對象,而是留出一個接口 ,利用 set 方法,代碼以下:
public class UserServiceImpl implements UserService {
private UserDao userDao;
// 利用set實現
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void getUser() {
userDao.getUser();
}
}
複製代碼
如今在測試類裏,進行測試:
public class UserGetTest {
@Test
public void getUser(){
UserServiceImpl userService = new UserServiceImpl();
userService.setUserDao(new UserDaoImpl());
userService.getUser();
userService.setUserDao(new UserDaoMysqlImpl());
userService.getUser();
}
}
複製代碼
執行結果爲:
從bean中獲取到的用戶數據爲User{name='hresh'}
從MySQL數據庫中獲取到的用戶數據爲User{name='acorn'}
複製代碼
雖然只是 UserServiceImpl 類中的代碼作了修改,看起來變更不大,甚至你可能會說測試類中還變複雜了。可是仔細想一下,以前全部的 Dao 實現類都是在 UserServiceImpl 中控制建立,而如今由更接近用戶的測試類中控制建立對象,把主動權交給了調用者,程序不用去管怎麼建立,怎麼實現了,它只負責提供一個接口便可。
這種思想 ,從本質上解決了問題 , 咱們程序員再也不去管理對象的建立了,更多的去關注業務的實現 ,耦合性大大下降 。這也就是 IoC 的原型 !
IoC( Inverse of Control:控制反轉 )是一種設計思想,就是將本來在程序中手動建立對象的控制權,交由 Spring 框架來管理。IoC 在其餘語言中也有應用,並不是 Spring 特有。IoC 容器是 Spring 用來實現 IoC 的載體,IoC 容器實際上就是個 Map(key,value),Map 中存放的是各類對象。
要了解控制反轉,有必要先了解軟件設計的一個重要思想:依賴倒置原則( Dependency Inversion Principle )。
- 高層模塊不該該依賴於底層模塊,二者應該依賴於其抽象。
- 抽象不該該依賴具體實現,具體實現應該依賴抽象。
上面2點是依賴倒置原則的概念,也是核心。主要是說模塊之間不要依賴具體實現,依賴接口或抽象。
其實依賴倒置原則的核心思想是面向接口編程。
將對象之間的相互依賴關係交給 IoC 容器來管理,並由 IoC 容器完成對象的注入。這樣能夠很大程度上簡化應用的開發,把應用從複雜的依賴關係中解放出來。IoC 容器就像是一個工廠同樣,當咱們須要建立一個對象的時候,只須要配置好配置文件/註解便可,徹底不用考慮對象是如何被建立出來的。在實際項目中一個 Service 類可能有幾百甚至上千個類做爲它的底層,假如咱們須要實例化這個 Service,你可能要每次都要搞清楚這個 Service 全部底層類的構造函數,這可能會把人逼瘋。若是利用 IoC 的話,你只須要配置好,而後在須要的地方引用就好了,這大大增長了項目的可維護性且下降了開發難度。
IoC 在 Spring 中有多種實現方式,可使用 XML 配置,也可使用註解,新版本的 Spring 也能夠零配置實現 IoC。
Spring 容器在初始化時先讀取配置文件,根據配置文件或元數據建立與組織對象存入容器中,程序使用時再從 IoC 容器中取出須要的對象。
採用 XML 方式配置 Bean 的時候,Bean 的定義信息是和實現分離的,而採用註解的方式能夠把二者合爲一體,Bean 的定義信息直接以註解的形式定義在實現類中,從而達到了零配置的目的。
定義一個 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);
}
}
複製代碼
執行結果爲:
User{name='hresh'}
複製代碼
User 對象是誰建立的?【user 對象是由 Spring 建立的】
User 對象的屬性是怎麼設置的?【user 對象的屬性是由 Spring 容器設置的】
這個過程就叫作控制反轉:
控制:誰來控制對象的建立,傳統應用程序的對象是由程序自己控制建立的,使用 Spring 後,對象是由 Spring 來建立的。
反轉:程序自己不建立對象,而變成被動地接收對象。
依賴注入:利用 set 方法來進行注入的。
IOC是一種編程思想,由主動的編程變成被動的接收 。
關於 ClassPathXmlApplicationContext 的學習後續會單獨介紹,有興趣的朋友能夠去看一下。
按照上述的方式咱們對以前提到的業務場景進行修改。首先新增 一個 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>
複製代碼
測試代碼以下:
@Test
public void MyBean(){
ApplicationContext context = new ClassPathXmlApplicationContext("application_context.xml");
UserServiceImpl serviceImpl = (UserServiceImpl) context.getBean("serviceImpl");
serviceImpl.getUser();
}
複製代碼
以後咱們不須要再去程序中改動了,要實現不一樣的操做,只須要在 XML 配置文件中進行修改。所謂的 IoC 就是對象由 Spring 來建立、管理和裝配。
在上文測試代碼中咱們用到的是 ApplicationContext,具體實現是 ClassPathXmlApplicationContext。因此接下來咱們簡單分析一下在此過程當中涉及到的組件。
首先是 ClassPathXmlApplicationContext 類的繼承關係圖。
基本上包含了 IOC 體系中大部分的核心類和接口。 下面咱們就針對這個圖進行簡單的拆分和補充說明。
Resource 主要負責對資源的抽象,它的每個實現類都表明了一種資源的訪問策略,如 ClasspathResource 、 URLResource ,FileSystemResource 等。
有了資源,就須要有資源加載模塊,Spring 利用 ResourceLoader 來進行統一資源加載,關係圖以下:
資源加載完畢以後就須要 BeanFactory 來進行加載解析,它是一個 bean 容器,其中 BeanDefinition 是它的基本結構,它內部維護着一 個 BeanDefinition map ,並可根據 BeanDefinition 的描述進行 bean 的建立和管理。
BeanFacoty 有三個直接子類 ListableBeanFactory
、HierarchicalBeanFactory
和 AutowireCapableBeanFactory
,DefaultListableBeanFactory
爲最終默認實現,它實現了全部接口。
BeanDefinition 用來描述 Spring 中的 Bean 對象。
BeanDefinitionReader 的做用是讀取 Spring 配置文件中的內容,將其轉換爲 IoC 容器內部的數據結構:BeanDefinition。
ApplicationContext 是個 Spring 容器,也叫作應用上下文。它繼承 BeanFactory,同時也是 BeanFactory 的擴展升級版。因爲 ApplicationContext 的結構就決定了它與 BeanFactory 的不一樣,其主要區別有:
上述提到的六個重要知識點是 Spring IoC 中最核心的部分,後續的學習也是針對這些內容進行詳細解讀。
當對象由無參構造器建立時,屬性是由該類的 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");
//在執行getBean的時候, user已經建立好了,屬性是經過set方法寫入的
User user = (User) context.getBean("user");
System.out.println(user);
}
}
複製代碼
執行結果爲:
user無參構造方法
User{name='hresh'}
複製代碼
若是將 User 類中的 set 方法註釋掉,再次調用測試代碼,會報錯,說明對象是由無參構造器建立成功後,會調用 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);
}
}
複製代碼
執行結果爲:
user有參構造方法
User{name='hresh'}
複製代碼
結論:Spring 容器根據 XML 文件中的配置,調用 bean 類的有參構造器來建立對象。
別名
alias 設置別名 , 爲bean設置別名 , 能夠設置多個別名 。
<!--設置別名:在獲取Bean的時候可使用別名獲取-->
<alias name="userT" alias="userNew"/>
複製代碼
Bean的配置
<!--bean就是java對象,由Spring建立和管理-->
<!--
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來實現 ,當有多個關於 bean 定義的文件,最後能夠集中在一個文件中。
<import resource="{path}/beans.xml"/>
複製代碼
https://blog.kuangstudy.com/index.php/archives/518/