一.前言
lz最近打算再過一遍Spring,溫故而知新,想當年初入IT時不知從何入手,甚是捉急,IOC、AOP看了無數遍也不知其解,隨着工作經驗的累積,漸漸悟到了學習路線,整理出來,供大家研討。
二. 本編講點
無論你是小白還是老司機,Spring三大特性一定背吐了:
1.IOC(控制反轉) 2.DI(依賴注入) 3.AOP(面向切面);
其中,IOC和DI是緊密相連的,所以本篇文章主要講一講什麼是IOC/DI,到底有什麼用!
三.什麼是IOC,有什麼用?
首先IOC是個容器,官方一點的話來說,最主要是完成了完成對象的創建和依賴的管理注入等等,這句話大家應該都知道,個人理解就是簡化了開發,直接上代碼感受一下,我們模擬一個購物的場景
/**
* 〈商場〉<br>
*
* @author Mr.Left
* @create 2018-12-15
* @since 1.0.0
*/
public class Shop {
public Person person;
public String name;
public Shop(Person person) {
this.person = person;
setName("打開淘寶");
}
public void open() {
System.out.println(getName());
person.say();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Person {
public Pick pick;
private String name;
public Person(Pick pick) {
this.pick = pick;
setName("Jack MA");
}
public void say() {
System.out.println("我是"+getName()+",我要開始買東西了!");
pick.pick();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
/**
* 〈挑選物品〉<br>
*
* @author Mr.Left
* @create 2018-12-15
* @since 1.0.0
*/
public class Pick {
public Pay pay;
private String commodity;
public Pick(Pay pay) {
this.pay = pay;
setCommodity("防脫髮洗髮液");
}
public void pick() {
System.out.println("我挑選了" + getCommodity());
pay.pay();
}
public String getCommodity() {
return commodity;
}
public void setCommodity(String commodity) {
this.commodity = commodity;
}
}
/**
* 〈挑選物品〉<br>
*
* @author Mr.Left
* @create 2018-12-15
* @since 1.0.0
*/
public class Pick {
public Pay pay;
private String commodity;
public Pick(Pay pay) {
this.pay = pay;
setCommodity("防脫髮洗髮液");
}
public void pick() {
System.out.println("我挑選了" + getCommodity());
pay.pay();
}
public String getCommodity() {
return commodity;
}
public void setCommodity(String commodity) {
this.commodity = commodity;
}
}
ok了,這一個簡單的場景就模擬出來了,爲了能更好的體會IOC的好處,Lz特意寫的麻煩些,再寫個測試接口跑一下
/**
* 〈一句話功能簡述〉<br>
* 〈測試接口〉
*
* @author Mr.Left
* @create 2018-12-15
* @since 1.0.0
*/
@RestController
@RequestMapping(value = "/test")
public class TestController {
@RequestMapping(value = "/demo")
@ResponseBody
public String demo() {
Pay pay = new Pay();
Pick pick = new Pick(pay);
Person person = new Person(pick);
Shop shop = new Shop(person);
shop.open();
return "ok";
}
}
好了,用原始的方法,我們通過對象的傳遞來維護依賴關係,那麼我們用spring的情況下,該怎麼寫呢?
/**
* 〈商場〉<br>
*
* @author Mr.Left
* @create 2018-12-15
* @since 1.0.0
*/
@Component(value = "SpringShop")
public class Shop {
@Autowired
private Person person;
public String name;
public Shop() {
setName("打開淘寶");
}
public void open() {
System.out.println(getName());
person.say();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
/**
* 〈顧客〉<br>
*
* @author Mr.Left
* @create 2018-12-15
* @since 1.0.0
*/
@Component(value = "SpringPerson")
public class Person {
@Autowired
private Pick pick;
private String name;
public void say() {
System.out.println("我是" + getName() + ",我要開始買東西了!");
pick.pick();
}
public Person() {
setName("Jack MA");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
/**
* 〈挑選物品〉<br>
*
* @author Mr.Left
* @create 2018-12-15
* @since 1.0.0
*/
@Component(value = "SpringPick")
public class Pick {
@Autowired
private Pay pay;
private String commodity;
public Pick() {
setCommodity("防脫髮洗髮液");
}
public void pick() {
System.out.println("我挑選了" + getCommodity());
pay.pay();
}
public String getCommodity() {
return commodity;
}
public void setCommodity(String commodity) {
this.commodity = commodity;
}
}
/**
* 〈付款〉<br>
* @author Mr.Left
* @create 2018-12-15
* @since 1.0.0
*/
@Component(value = "SpringPay")
public class Pay {
private String payWay;
public Pay(){
setPayWay("支付寶");
}
public String getPayWay() {
return payWay;
}
public void setPayWay(String payWay) {
this.payWay = payWay;
}
public void pay() {
System.out.println("我用" + getPayWay() + "付款");
}
}
/**
* 〈一句話功能簡述〉<br>
* 〈測試接口〉
*
* @author Mr.Left
* @create 2018-12-15
* @since 1.0.0
*/
@RestController(value = "SpringTestController")
@RequestMapping(value = "/test2")
public class TestController {
@Autowired
private com.koolearn.donut.practise.spring.Shop shop;
@RequestMapping(value = "/demo")
@ResponseBody
public String demo() {
shop.open();
return "ok";
}
}
怎麼樣,用了註解之後是不是感覺一身輕鬆,在實際項目中,一個對象不可能只依賴一個對象,大多數是依賴多個甚至十幾個,並且依賴關係是n層,維護起來讓人崩潰,有了Spring,你不用關心對象的實例化,更不用維護依賴關係,真正的實現瞭解耦!這就是程序員的春天,哈哈。
By the way,spring注入的另一種方式是xml配置,這裏就不說了
四.理解IOC思想
lz覺得編程難點,是難在理解代碼的思想,因爲它是抽象的,和數學物理一樣,看不見摸不着,正確理解並掌握了設計思想,纔會從碼農進階爲互聯網精英,如果你是一個善於思考的人,可能會發現我們所學習到的知識似乎都遵循着某種規律,即使再抽象的東西,其本質思想,也會跟現實生活中有點聯繫,就拿IOC來說,就是把new對象的權利交給了Spring容器來處理,在現實中,不就是把一些自己嫌麻煩的事情,交給第三方來處理嘛;舉個栗子,n多年前我們打車去一個地方,只能出門攔空車,節假日和大冬天一等就是好久,有時還要拼車,你需要告訴司機你去哪,司機看順不順路,再考慮是否帶上你,這就可以看成是自己new 一個對象(出租車),乘客的目的地就像是一種依賴關係;現在好了,下一個app,輸入起點終點,交給平臺(容器)就什麼也不用管了,美滋滋。
好了,有點扯遠了,知道了IOC的作用,我們接下來看看源碼,研究一下是怎麼實現的吧!
五.Spring IOC的結構體系
(1)BeanFactory
大名鼎鼎的Bean工廠,Spring中Bean的創建是典型的工廠模式,看一下代碼:
package org.springframework.beans.factory;
import org.springframework.beans.BeansException;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
public interface BeanFactory {
String FACTORY_BEAN_PREFIX = "&";
Object getBean(String var1) throws BeansException;
<T> T getBean(String var1, Class<T> var2) throws BeansException;
Object getBean(String var1, Object... var2) throws BeansException;
<T> T getBean(Class<T> var1) throws BeansException;
<T> T getBean(Class<T> var1, Object... var2) throws BeansException;
<T> ObjectProvider<T> getBeanProvider(Class<T> var1);
<T> ObjectProvider<T> getBeanProvider(ResolvableType var1);
boolean containsBean(String var1);
boolean isSingleton(String var1) throws NoSuchBeanDefinitionException;
boolean isPrototype(String var1) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String var1, ResolvableType var2) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String var1, Class<?> var2) throws NoSuchBeanDefinitionException;
@Nullable
Class<?> getType(String var1) throws NoSuchBeanDefinitionException;
String[] getAliases(String var1);
}
第一次看源碼的時候一臉矇蔽,包那麼多,那麼複雜,不過研究東西就要靜下心來,這個就是Spring核心的Bean工廠定義,好像是2001年寫的,這個類是Spring中所有的bean工廠,也就是俗稱IOC容器的祖宗(膜拜3秒鐘),各種IOC容器都只是它的實現或者爲了滿足特別需求的擴展實現,包括我們平時用的ApplicationContext。從接口中的方法名字不難看出,這些工廠的實現最大的作用就是根據bean的名稱或者類型等等,返回一個bean的實例。
來看一下他的關係圖
BeanFactory作爲最頂層的一個接口類,它定義了IOC容器的基本功能規範,BeanFactory 有三個子類:ListableBeanFactory、HierarchicalBeanFactory 和AutowireCapableBeanFactory。但是從圖中能發現最終的默認實現類是 DefaultListableBeanFactory,他實現了所有的接口。那爲何要定義這麼多層次的接口呢?查閱這些接口的源碼和說明發現,每個接口都有他使用的場合,它主要是爲了區分在 Spring 內部在操作過程中對象的傳遞和轉化過程中,對對象的數據訪問所做的限制。例如 ListableBeanFactory 接口表示這些 Bean 是可列表的,而 HierarchicalBeanFactory 表示的是這些 Bean 是有繼承關係的,也就是每個Bean 有可能有父 Bean。AutowireCapableBeanFactory 接口定義 Bean 的自動裝配規則。這四個接口共同定義了 Bean 的集合、Bean 之間的關係、以及 Bean 行爲。
現在我們來思考一件事情:一個工廠想要擁有這個功能,需要具備幾個因素呢?
1.需要持有各種Bean的定義,否則無法正確的完成bean的實例化;
2.需要持有bean之間的依賴關係,否則在實例化過程中也會出現問題。例如上例,我們只是各自持有person和shop,卻不知道他們的依賴關係,那麼在shop初始化後,調用open方法時,就會報空指針。這是因爲shop其實沒有真正的被正確的實例化。
3.以上兩種都要依賴我們寫依賴關係的定義,我們暫且認爲是xml文件,那麼我們需要一個工具來讀取它
在我的理解中,只要滿足上述三種條件,便可以創建一個bean工廠,當然,Spring還有更高級的做法,以上只是我直觀的去想如何實現IOC。
接下來接着思考,第一步,如何持有Bean的定義?我們知道在Spring的Xml配置文件中,有個lazy-init的屬性,這就說明我們是可以控制bean在何時實例化的。這個屬性默認是false的,也就是說Spring容器初始化後,配置了延遲加載的bean都還未產生,只是存儲了bean的定義,而非實例,在需要的時候,它纔會出現,接下來,再介紹一個祖宗級別的接口:BeanDefinition
package org.springframework.beans.factory.config;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.core.AttributeAccessor;
import org.springframework.lang.Nullable;
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
String SCOPE_SINGLETON = "singleton";
String SCOPE_PROTOTYPE = "prototype";
int ROLE_APPLICATION = 0;
int ROLE_SUPPORT = 1;
int ROLE_INFRASTRUCTURE = 2;
void setParentName(@Nullable String var1);
@Nullable
String getParentName();
void setBeanClassName(@Nullable String var1);
@Nullable
String getBeanClassName();
void setScope(@Nullable String var1);
@Nullable
String getScope();
void setLazyInit(boolean var1);
boolean isLazyInit();
void setDependsOn(@Nullable String... var1);
@Nullable
String[] getDependsOn();
void setAutowireCandidate(boolean var1);
boolean isAutowireCandidate();
void setPrimary(boolean var1);
boolean isPrimary();
void setFactoryBeanName(@Nullable String var1);
@Nullable
String getFactoryBeanName();
void setFactoryMethodName(@Nullable String var1);
@Nullable
String getFactoryMethodName();
ConstructorArgumentValues getConstructorArgumentValues();
default boolean hasConstructorArgumentValues() {
return !this.getConstructorArgumentValues().isEmpty();
}
MutablePropertyValues getPropertyValues();
default boolean hasPropertyValues() {
return !this.getPropertyValues().isEmpty();
}
void setInitMethodName(@Nullable String var1);
@Nullable
String getInitMethodName();
void setDestroyMethodName(@Nullable String var1);
@Nullable
String getDestroyMethodName();
void setRole(int var1);
int getRole();
void setDescription(@Nullable String var1);
@Nullable
String getDescription();
boolean isSingleton();
boolean isPrototype();
boolean isAbstract();
@Nullable
String getResourceDescription();
@Nullable
BeanDefinition getOriginatingBeanDefinition();
}
SpringIOC容器管理了我們定義的各種Bean對象及其相互的關係,Bean對象在Spring實現中是以BeanDefinition來描述的,其繼承體系如下:
Bean 的解析過程非常複雜,功能被分的很細,因爲這裏需要被擴展的地方很多,必須保證有足夠的靈活性,以應對可能的變化。Bean 的解析主要就是對 Spring 配置文件的解析。這個解析過程主要通過下圖中的類完成:
再看看源碼,這個便是spring中的bean定義接口,所以其實我們工廠裏持有的bean定義,就是一堆這個玩意,或者是他的實現類和子接口。這個接口並非直接的祖宗接口,他所繼承的兩個接口一個是core下面的AttributeAccessor,繼承這個接口就以爲這我們的bean定義接口同樣具有處理屬性的能力,而另外一個是beans下面的BeanMetadataElement,字面翻譯這個接口就是bean的元數據元素,它可以獲得bean的配置定義的一個元素。在XML文件中來說,就是會持有一個bean標籤。
仔細觀看,能發現beanDefinition中有兩個方法分別是String[] getDependsOn()和void setDependsOn(String[] dependsOn),這兩個方法就是獲取依賴的beanName和設置依賴的beanName,這樣就好辦了,只要我們有一個BeanDefinition,就可以完全的產生一個完整的bean實例。
今天就先到這裏吧,lz菜的摳腳,也是邊看邊寫的,費了很大勁才梳理出一點東西,更多的是看到哪寫到哪,IOC的實現原理用到了反射,我接下來想想怎麼寫進去。
文中若有欠妥的地方,希望大家指出。