手寫IOC容器-探究IOC的本質(zhì)原理
點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”
優(yōu)質(zhì)文章,第一時(shí)間送達(dá)
66套java從入門到精通實(shí)戰(zhàn)課程分享
? 作者?|??hello-*-world
來源 |? cnblogs.com/HTLucky/p/13379900.html
IOC(控制翻轉(zhuǎn))是程序設(shè)計(jì)的一種思想,其本質(zhì)就是上端對(duì)象不能直接依賴于下端對(duì)象,要是依賴的話就要通過抽象來依賴。這是什么意思呢?意思就是上端對(duì)象如BLL層中,需要調(diào)用下端對(duì)象的DAL層時(shí)不能直接調(diào)用DAl的具體實(shí)現(xiàn),而是通過抽象的方式來進(jìn)行調(diào)用。這樣做是有一定的道理的。有這么一個(gè)場(chǎng)景,你們的項(xiàng)目本來是用Sqlserver來進(jìn)行數(shù)據(jù)訪問的,那么就會(huì)有一個(gè)SqlserverDal對(duì)象。BLL層調(diào)用的時(shí)候通過new SqlserverDal(),直接創(chuàng)建一個(gè)SqlserverDal對(duì)象進(jìn)行數(shù)據(jù)訪問,現(xiàn)在項(xiàng)目又要改為Mysql數(shù)據(jù)庫,用MysqlDal進(jìn)行數(shù)據(jù)訪問。這時(shí)候就麻煩了,你的BLL層將new SqlserverDal()全部改為new MysqlDal()。同理BLL層也是這個(gè)道理。這么做,從程序的架構(gòu)而言是相當(dāng)不合理的,我只是想將SqlserverDal替換為MysqlDal。按道理說我只要添加MysqlDal對(duì)象就可以了。可現(xiàn)在的做法是還要將BLL中的new SqlserverDal()全部改一遍。這未免有點(diǎn)得不償失了。這時(shí)IOC就排上用場(chǎng)了,IOC的核心理念就是上端對(duì)象通過抽象來依賴下端對(duì)象,那么我們?cè)贐LL中,不能直接通過new SqlserverDal()來創(chuàng)建一個(gè)對(duì)象,而是通過結(jié)構(gòu)來聲明(抽象的形式來進(jìn)行依賴),當(dāng)我們替換MysqlDal時(shí)我們只需讓MysqlDal也繼承這個(gè)接口,那么我們BLL層的邏輯就不用動(dòng)了。那么現(xiàn)在又有一個(gè)問題,對(duì)象我們可以用接口來接收,所有子類出現(xiàn)的地方都可以用父類來替代,這沒毛病。但對(duì)象的創(chuàng)建還是要知道具體的類型,還是通過之前的new SqlserverDal()這種方式創(chuàng)建對(duì)象。肯定是不合理的,這里我們還是依賴于細(xì)節(jié)。
那我們需要怎么處理呢?這時(shí)候IOC容器就該上場(chǎng)了,IOC容器可以理解為一個(gè)第三方的類,專門為我們創(chuàng)建對(duì)象用的,它不需要關(guān)注具體的業(yè)務(wù)邏輯,也不關(guān)注具體的細(xì)節(jié)。你只需將你需要的創(chuàng)建的對(duì)象類型傳給它,它就能幫我們完成對(duì)象的創(chuàng)建。常見的IOC容器有Autofac,Unity
接觸.net core的小伙伴可能對(duì)容器很熟悉,.net core中將IOC容器內(nèi)置了。創(chuàng)建對(duì)象需要先進(jìn)行注冊(cè)
public?void?ConfigureServices(IServiceCollection services)
????????{
????????????services.AddTransient();
????????????services.AddTransient();
????????} 從上面的示例我們可以看到.net core中是通過ServiceCollection容器幫我們完成對(duì)象的創(chuàng)建,我們只需將接口的類型和要?jiǎng)?chuàng)建對(duì)象的類型傳進(jìn)去,它就能幫我們完成對(duì)象的創(chuàng)建。那么它的原理是啥呢,我們能不能創(chuàng)建自已的容器來幫我們完成對(duì)象的創(chuàng)建呢,讓我們帶著疑惑繼續(xù)往下走
一.容器雛形
這里我們先不考慮那么多,我們先寫一個(gè)容器,幫我們完成對(duì)象的創(chuàng)建工作。
public??class?HTContainer : IHTContainer
????{
????????//創(chuàng)建一個(gè)Dictionary數(shù)據(jù)類型的對(duì)象用來存儲(chǔ)注冊(cè)的對(duì)象
????????private?Dictionary<string, Type> TypeDictionary = new?Dictionary<string, Type>();
????????//注冊(cè)方法,用接口的FullName為key值,value為要?jiǎng)?chuàng)建對(duì)象的類型
????????public?void?RegisterType()
????????{
????????????this.TypeDictionary.Add(typeof(IT).FullName, typeof(T));
????????}
????????//創(chuàng)建對(duì)象通過傳遞的類型進(jìn)行匹配
????????public?IT Resolve()
????????{
????????????string?key = typeof(IT).FullName;
????????????Type type?= this.TypeDictionary[key]; //獲取要?jiǎng)?chuàng)建對(duì)象的類型
????????????//這里先不考慮有參構(gòu)造函數(shù)的問題,后面會(huì)逐一的解決這些問題
????????????return??(IT)Activator.CreateInstance(type); //通過反射完成對(duì)象的創(chuàng)建,這里我們先不考慮參數(shù)問題
????????????
????????}
????} 簡單調(diào)用
//實(shí)例化容器對(duì)象
????????????IHTContainer container = new?HTContainer();
????????????//注冊(cè)對(duì)象
????????????container.RegisterType();
????????????//通過容器完成對(duì)象的創(chuàng)建,不體現(xiàn)細(xì)節(jié),用抽象完成對(duì)象的創(chuàng)建
????????????IDatabase dal = container.Resolve();
????????????dal.Connection("con"); 通過上邊的一頓操作,我們做了什么事呢?我們完成了一個(gè)大的飛躍,通常創(chuàng)建對(duì)象我們是直接new一個(gè),現(xiàn)在我們是通過一個(gè)第三方的容器為我們創(chuàng)建對(duì)象,并且我們不用依賴于細(xì)節(jié),通過接口的類型完成對(duì)象的創(chuàng)建,當(dāng)我們要將SqlserverDal替換為MysqlDal時(shí),我們只需要在注冊(cè)的時(shí)候?qū)qlserverDal替換為MysqlDal即可
?二.升級(jí)改造容器(解決參數(shù)問題)
上面我們將傳統(tǒng)對(duì)象創(chuàng)建的方式,改為使用第三方容器來幫我們完成對(duì)象的創(chuàng)建。但這個(gè)容器考慮的還不是那么的全面,例如有參構(gòu)造的問題,以及對(duì)象的依賴問題我們還沒有考慮到,接下來我們繼續(xù)完善這個(gè)容器,這里我們先不考慮多個(gè)構(gòu)造函數(shù)的問題。這里先解決只有一個(gè)構(gòu)造函數(shù)場(chǎng)景的參數(shù)問題
1.構(gòu)造函數(shù)只有一個(gè)參數(shù)的情況
//創(chuàng)建對(duì)象通過傳遞的類型進(jìn)行匹配
????????public?IT Resolve()
????????{
????????????string?key = typeof(IT).FullName;
????????????Type type?= this.TypeDictionary[key]; //獲取要?jiǎng)?chuàng)建對(duì)象的類型
????????????var?ctor = type.GetConstructors()[0]; //這里先考慮只有一個(gè)構(gòu)造函數(shù)的場(chǎng)景
???????????
????????????//一個(gè)參數(shù)的形式
????????????var?paraList = ctor.GetParameters();
????????????var?para = paraList[0];
????????????Type paraInterfaceType = para.ParameterType;
????????????Type paraType = this.TypeDictionary[paraInterfaceType.FullName]; //還是要先獲取依賴對(duì)象的類型
????????????object oPara = Activator.CreateInstance(paraType); //創(chuàng)建參數(shù)中所依賴的對(duì)象
????????????return?(IT)Activator.CreateInstance(type,oPara); //創(chuàng)建對(duì)象并傳遞所依賴的對(duì)象
????????} 2.構(gòu)造函數(shù)多參數(shù)的情況
上面我們解決了構(gòu)造函數(shù)只有一個(gè)參數(shù)的問題,我們是通過構(gòu)造函數(shù)的類型創(chuàng)建一個(gè)對(duì)象,并將這個(gè)對(duì)象作為參數(shù)傳遞到要實(shí)例化的對(duì)象中。那么多參數(shù)我們就需要?jiǎng)?chuàng)建多個(gè)參數(shù)的對(duì)象傳遞到要實(shí)例的對(duì)象中
//創(chuàng)建對(duì)象通過傳遞的類型進(jìn)行匹配
????????public?IT Resolve()
????????{
????????????string?key = typeof(IT).FullName;
????????????Type type = this.TypeDictionary[key]; //獲取要?jiǎng)?chuàng)建對(duì)象的類型
????????????var?ctor = type.GetConstructors()[0]; //這里先考慮只有一個(gè)構(gòu)造函數(shù)的場(chǎng)景
???????????
????????????//多個(gè)參數(shù)的形式
????????????List<object> paraList = new?List<object>(); //聲明一個(gè)list來存儲(chǔ)參數(shù)類型的對(duì)象
????????????foreach?(var?para in?ctor.GetParameters())
????????????{
????????????????Type paraInterfaceType = para.ParameterType;
????????????????Type paraType = this.TypeDictionary[paraInterfaceType.FullName];
????????????????object?oPara = Activator.CreateInstance(paraType);
????????????????paraList.Add(oPara);
????????????}
????????????return?(IT)Activator.CreateInstance(type, paraList.ToArray()); //創(chuàng)建對(duì)象并傳遞所依賴的對(duì)象數(shù)組
????????} ?3.解決對(duì)象的循環(huán)依賴問題
通過上面的兩步操作,我們已經(jīng)能對(duì)構(gòu)造函數(shù)中的參數(shù)初始化對(duì)象并傳遞到要實(shí)例的對(duì)象中,但這只是一個(gè)層級(jí)的。我們剛才做的只是解決了這么一個(gè)問題,假設(shè)我們要?jiǎng)?chuàng)建A對(duì)象,A對(duì)象依賴于B對(duì)象。我們做的就是創(chuàng)建了B對(duì)象作為參數(shù)傳遞給A并創(chuàng)建A對(duì)象,這只是一個(gè)層級(jí)的。當(dāng)B對(duì)象又依賴于C對(duì)象,C對(duì)象又依賴于D對(duì)象,這么一直循環(huán)下去。這樣的場(chǎng)景我們?cè)撛趺唇鉀Q呢?下面我們將通過遞歸的方式來解決這一問題
//創(chuàng)建對(duì)象通過傳遞的類型進(jìn)行匹配
????????public?IT Resolve()
????????{
????????????return?(IT)this.ResolveObject(typeof(IT));
????????}
????????//通過遞歸的方式創(chuàng)建多層級(jí)的對(duì)象
????????private?object?ResolveObject(Type abstractType)
????????{
????????????string?key = abstractType.FullName;
????????????Type type = this.TypeDictionary[key]; //獲取要?jiǎng)?chuàng)建對(duì)象的類型
????????????var?ctor = type.GetConstructors()[0];
????????????//多個(gè)參數(shù)的形式
????????????List<object> paraList = new?List<object>();
????????????foreach?(var?para in?ctor.GetParameters())
????????????{
????????????????Type paraInterfaceType = para.ParameterType;
????????????????Type paraType = this.TypeDictionary[paraInterfaceType.FullName];
????????????????object?oPara = ResolveObject(paraInterfaceType); //自已調(diào)用自己,實(shí)現(xiàn)遞歸操作,完成各個(gè)層級(jí)對(duì)象的創(chuàng)建
????????????????paraList.Add(oPara);
????????????}
????????????return?(object)Activator.CreateInstance(type, paraList.ToArray());
????????} 三.繼續(xù)升級(jí)(考慮多個(gè)構(gòu)造函數(shù)的問題)
上面我們只是考慮了只有一個(gè)構(gòu)造函數(shù)的問題,那初始化的對(duì)象有多個(gè)構(gòu)造函數(shù)我們?cè)撊绾翁幚砟兀覀兛梢韵馎utofac那樣選擇一個(gè)參數(shù)最多的構(gòu)造函數(shù),也可以像ServiceCollection那樣選擇一個(gè)參數(shù)的超集來進(jìn)行構(gòu)造,當(dāng)然我們也可以聲明一個(gè)特性,那個(gè)構(gòu)造函數(shù)中標(biāo)記了這個(gè)特性,我們就采用那個(gè)構(gòu)造函數(shù)。
//通過遞歸的方式創(chuàng)建多層級(jí)的對(duì)象
????????private?object ResolveObject(Type abstractType)
????????{
????????????string?key = abstractType.FullName;
????????????Type type?= this.TypeDictionary[key]; //獲取要?jiǎng)?chuàng)建對(duì)象的類型
????????????var?ctorArray = type.GetConstructors(); //獲取對(duì)象的所有構(gòu)造函數(shù)
????????????ConstructorInfo ctor = null;
????????????//判斷構(gòu)造函數(shù)中是否標(biāo)記了HTAttribute這個(gè)特性
????????????if?(ctorArray.Count(c?=>?c.IsDefined(typeof(HTAttribute), true)) > 0)
????????????{
????????????????//若標(biāo)記了HTAttribute特性,默認(rèn)就采用這個(gè)構(gòu)造函數(shù)
????????????????ctor = ctorArray.FirstOrDefault(c?=>?c.IsDefined(typeof(HTAttribute), true));
????????????}
????????????else
????????????{
????????????????//若都沒有標(biāo)記特性,那就采用構(gòu)造函數(shù)中參數(shù)最多的構(gòu)造函數(shù)
????????????????ctor = ctorArray.OrderByDescending(c?=>?c.GetParameters().Length).FirstOrDefault();
????????????}
?????
????????????//多個(gè)參數(shù)的形式
????????????List上面的操作我們通過依賴注入的方式完成了對(duì)容器的升級(jí),那么依賴注入到底是啥呢?
依賴注入(Dependency Injection,簡稱DI)就是構(gòu)造A對(duì)象時(shí),需要依賴B對(duì)象,那么就先構(gòu)造B對(duì)象作為參數(shù)傳遞到A對(duì)象,這種對(duì)象初始化并注入的技術(shù)就叫做依賴注入。IOC是一種設(shè)計(jì)模式,程序架構(gòu)的目標(biāo)。DI是IOC的實(shí)現(xiàn)手段


新款SpringBoot在線教育平臺(tái)開源了
50份優(yōu)秀Java求職者簡歷
SpringCloud前后端分離實(shí)戰(zhàn)項(xiàng)目視頻教程分享
2020年全網(wǎng)最全BAT筆試面試題打包分享
??? ?
感謝點(diǎn)贊支持下哈?![]()
