摘要:從而能夠進(jìn)一步深入了解框架。至此我們框架開發(fā)完成。雖然說閱讀源碼是了解框架的最終手段。但是框架作為一個生產(chǎn)框架,為了保證通用和穩(wěn)定,源碼必定是高度抽象,且處理大量細(xì)節(jié)。下一篇文章應(yīng)該會是徒手?jǐn)]框架實現(xiàn)。
原文地址:https://www.xilidou.com/2018/...
Spring 作為 J2ee 開發(fā)事實上的標(biāo)準(zhǔn),是每個Java開發(fā)人員都需要了解的框架。但是Spring 的 IoC 和 Aop 的特性,對于初級的Java開發(fā)人員來說還是比較難于理解的。所以我就想寫一系列的文章給大家講解這些特性。從而能夠進(jìn)一步深入了解 Spring 框架。
讀完這篇文章,你將會了解:
什么是依賴注入和控制反轉(zhuǎn)
Ioc有什么用
Spring的 Ioc 是怎么實現(xiàn)的
按照Spring的思路開發(fā)一個簡單的Ioc框架
IoC 是什么?wiki百科的解釋是:
控制反轉(zhuǎn)(Inversion of Control,縮寫為IoC),是面向?qū)ο缶幊讨械囊环N設(shè)計原則,可以用來減低計算機(jī)代碼之間的耦合度。其中最常見的方式叫做依賴注入(Dependency Injection,簡稱DI)。通過控制反轉(zhuǎn),對象在被創(chuàng)建的時候,由一個調(diào)控系統(tǒng)內(nèi)所有對象的外界實體,將其所依賴的對象的引用傳遞給它。也可以說,依賴被注入到對象中。Ioc 有什么用?
看完上面的解釋你一定沒有理解什么是 Ioc,因為是第一次看見上面的話也覺得云里霧里。
不過通過上面的描述我們可以大概的了解到,使用IoC的目的是為了解耦。也就是說IoC 是解耦的一種方法。
我們知道Java 是一門面向?qū)ο蟮恼Z言,在 Java 中 Everything is Object,我們的程序就是由若干對象組成的。當(dāng)我們的項目越來越大,合作的開發(fā)者越來越多的時候,我們的類就會越來越多,類與類之間的引用就會成指數(shù)級的增長。如下圖所示:
這樣的工程簡直就是災(zāi)難,如果我們引入 Ioc 框架。由框架來維護(hù)類的生命周期和類之間的引用。我們的系統(tǒng)就會變成這樣:
這個時候我們發(fā)現(xiàn),我們類之間的關(guān)系都由 IoC 框架負(fù)責(zé)維護(hù)類,同時將類注入到需要的類中。也就是類的使用者只負(fù)責(zé)使用,而不負(fù)責(zé)維護(hù)。把專業(yè)的事情交給專業(yè)的框架來完成。大大的減少開發(fā)的復(fù)雜度。
用一個類比來理解這個問題。Ioc 框架就是我們生活中的房屋中介,首先中介會收集市場上的房源,分別和各個房源的房東建立聯(lián)系。當(dāng)我們需要租房的時候,并不需要我們四處尋找各類租房信息。我們直接找房屋中介,中介就會根據(jù)你的需求提供相應(yīng)的房屋信息。大大提升了租房的效率,減少了你與各類房東之間的溝通次數(shù)。
Spring 的 IoC 是怎么實現(xiàn)的了解Spring框架最直接的方法就閱讀Spring的源碼。但是Spring的代碼抽象的層次很高,且處理的細(xì)節(jié)很高。對于大多數(shù)人來說不是太容易理解。我讀了Spirng的源碼以后以我的理解做一個總結(jié),Spirng IoC 主要是以下幾個步驟。
1. 初始化 IoC 容器。 2. 讀取配置文件。 3. 將配置文件轉(zhuǎn)換為容器識別對的數(shù)據(jù)結(jié)構(gòu)(這個數(shù)據(jù)結(jié)構(gòu)在Spring中叫做 BeanDefinition) 4. 利用數(shù)據(jù)結(jié)構(gòu)依次實例化相應(yīng)的對象 5. 注入對象之間的依賴關(guān)系自己實現(xiàn)一個IoC框架
為了方便,我們參考 Spirng 的 IoC 實現(xiàn),去除所有與核心原理無關(guān)的邏輯。極簡的實現(xiàn) IoC 的框架。 項目使用 json 作為配置文件。使用 maven 管理 jar 包的依賴。
在這個框架中我們的對象都是單例的,并不支持Spirng的多種作用域??蚣艿膶崿F(xiàn)使用了cglib 和 Java 的反射。項目中我還使用了 lombok 用來簡化代碼。
下面我們就來編寫 IoC 框架吧。
首先我們看看這個框架的基本結(jié)構(gòu):
從宏觀上觀察一下這個框架,包含了3個package、在包 bean 中定義了我們框架的數(shù)據(jù)結(jié)構(gòu)。core 是我們框架的核心邏輯所在。utils 是一些通用工具類。接下來我們就逐一講解一下:
1. bean 定義了框架的數(shù)據(jù)結(jié)構(gòu)BeanDefinition 是我們項目的核心數(shù)據(jù)結(jié)構(gòu)。用于描述我們需要 IoC 框架管理的對象。
@Data @ToString public class BeanDefinition { private String name; private String className; private String interfaceName; private ListconstructorArgs; private List propertyArgs; }
包含了對象的 name,class的名稱。如果是接口的實現(xiàn),還有該對象實現(xiàn)的接口。以及構(gòu)造函數(shù)的傳參的列表 constructorArgs 和需要注入的參數(shù)列表 `propertyArgs。
2. 再看看我們的工具類包里面的對象:ClassUtils 負(fù)責(zé)處理 Java 類的加載,代碼如下:
public class ClassUtils { public static ClassLoader getDefultClassLoader(){ return Thread.currentThread().getContextClassLoader(); } public static Class loadClass(String className){ try { return getDefultClassLoader().loadClass(className); } catch (ClassNotFoundException e) { e.printStackTrace(); } return null; } }
我們只寫了一個方法,就是通過 className 這個參數(shù)獲取對象的 Class。
BeanUtils 負(fù)責(zé)處理對象的實例化,這里我們使用了 cglib 這個工具包,代碼如下:
public class BeanUtils { public staticT instanceByCglib(Class clz,Constructor ctr,Object[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(clz); enhancer.setCallback(NoOp.INSTANCE); if(ctr == null){ return (T) enhancer.create(); }else { return (T) enhancer.create(ctr.getParameterTypes(),args); } } }
ReflectionUtils 主要通過 Java 的反射原理來完成對象的依賴注入:
public class ReflectionUtils { public static void injectField(Field field,Object obj,Object value) throws IllegalAccessException { if(field != null) { field.setAccessible(true); field.set(obj, value); } } }
injectField(Field field,Object obj,Object value) 這個方法的作用就是,設(shè)置 obj 的 field 為 value。
JsonUtils 的作用就是為了解析我們的json配置文件。代碼比較長,與我們的 IoC 原理關(guān)系不大,感興趣的同學(xué)可以自行從github上下載代碼看看。
有了這幾個趁手的工具,我們就可以開始完成 Ioc 框架的核心代碼了。
3. 核心邏輯我的 IoC 框架,目前只支持一種 ByName 的注入。所以我們的 BeanFactory 就只有一個方法:
public interface BeanFactory { Object getBean(String name) throws Exception; }
然后我們實現(xiàn)了這個方法:
public class BeanFactoryImpl implements BeanFactory{ private static final ConcurrentHashMapbeanMap = new ConcurrentHashMap<>(); private static final ConcurrentHashMap beanDefineMap= new ConcurrentHashMap<>(); private static final Set beanNameSet = Collections.synchronizedSet(new HashSet<>()); @Override public Object getBean(String name) throws Exception { //查找對象是否已經(jīng)實例化過 Object bean = beanMap.get(name); if(bean != null){ return bean; } //如果沒有實例化,那就需要調(diào)用createBean來創(chuàng)建對象 bean = createBean(beanDefineMap.get(name)); if(bean != null) { //對象創(chuàng)建成功以后,注入對象需要的參數(shù) populatebean(bean); //再把對象存入Map中方便下次使用。 beanMap.put(name,bean; } //結(jié)束返回 return bean; } protected void registerBean(String name, BeanDefinition bd){ beanDefineMap.put(name,bd); beanNameSet.add(name); } private Object createBean(BeanDefinition beanDefinition) throws Exception { String beanName = beanDefinition.getClassName(); Class clz = ClassUtils.loadClass(beanName); if(clz == null) { throw new Exception("can not find bean by beanName"); } List constructorArgs = beanDefinition.getConstructorArgs(); if(constructorArgs != null && !constructorArgs.isEmpty()){ List
首先我們看到在 BeanFactory 的實現(xiàn)中。我們有兩 HashMap,beanMap 和 beanDefineMap。 beanDefineMap 存儲的是對象的名稱和對象對應(yīng)的數(shù)據(jù)結(jié)構(gòu)的映射。beanMap 用于保存 beanName和實例化之后的對象。
容器初始化的時候,會調(diào)用 BeanFactoryImpl.registerBean 方法。把 對象的 BeanDefination 數(shù)據(jù)結(jié)構(gòu),存儲起來。
當(dāng)我們調(diào)用 getBean() 的方法的時候。會先到 beanMap 里面查找,有沒有實例化好的對象。如果沒有,就會去beanDefineMap查找這個對象對應(yīng)的 BeanDefination。再利用DeanDefination去實例化一個對象。
對象實例化成功以后,我們還需要注入相應(yīng)的參數(shù),調(diào)用 populatebean()這個方法。在 populateBean 這個方法中,會掃描對象里面的Field,如果對象中的 Field 是我們IoC容器管理的對象,那就會調(diào)用 我們上文實現(xiàn)的 ReflectionUtils.injectField來注入對象。
一切準(zhǔn)備妥當(dāng)之后,我們對象就完成了整個 IoC 流程。最后這個對象放入 beanMap 中,方便下一次使用。
所以我們可以知道 BeanFactory 是管理和生成對象的地方。
4. 容器我們所謂的容器,就是對BeanFactory的擴(kuò)展,負(fù)責(zé)管理 BeanFactory。我們的這個IoC 框架使用 Json 作為配置文件,所以我們?nèi)萜骶兔麨?JsonApplicationContext。當(dāng)然之后你愿意實現(xiàn) XML 作為配置文件的容器你就可以自己寫一個 XmlApplicationContext,如果基于注解的容器就可以叫AnnotationApplcationContext。這些實現(xiàn)留個大家去完成。
我們看看 ApplicationContext 的代碼:
public class JsonApplicationContext extends BeanFactoryImpl{ private String fileName; public JsonApplicationContext(String fileName) { this.fileName = fileName; } public void init(){ loadFile(); } private void loadFile(){ InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName); ListbeanDefinitions = JsonUtils.readValue(is,new TypeReference >(){}); if(beanDefinitions != null && !beanDefinitions.isEmpty()) { for (BeanDefinition beanDefinition : beanDefinitions) { registerBean(beanDefinition.getName(), beanDefinition); } } } }
這個容器的作用就是 讀取配置文件。將配置文件轉(zhuǎn)換為容器能夠理解的 BeanDefination。然后使用 registerBean 方法。注冊這個對象。
至此,一個簡單版的 IoC 框架就完成。
5. 框架的使用我們寫一個測試類來看看我們這個框架怎么使用:
首先我們有三個對象
public class Hand { public void waveHand(){ System.out.println("揮一揮手"); } } public class Mouth { public void speak(){ System.out.println("say hello world"); } } public class Robot { //需要注入 hand 和 mouth private Hand hand; private Mouth mouth; public void show(){ hand.waveHand(); mouth.speak(); } }
我們需要為我們的 Robot 機(jī)器人注入 hand 和 mouth。
配置文件:
[ { "name":"robot", "className":"com.xilidou.framework.ioc.entity.Robot" }, { "name":"hand", "className":"com.xilidou.framework.ioc.entity.Hand" }, { "name":"mouth", "className":"com.xilidou.framework.ioc.entity.Mouth" } ]
這個時候?qū)懸粋€測試類:
public class Test { public static void main(String[] args) throws Exception { JsonApplicationContext applicationContext = new JsonApplicationContext("application.json"); applicationContext.init(); Robot aiRobot = (Robot) applicationContext.getBean("robot"); aiRobot.show(); } }
運(yùn)行以后輸出:
揮一揮手 say hello world Process finished with exit code 0
可以看到我們成功的給我的 aiRobot 注入了 hand 和 mouth。
至此我們 Ioc 框架開發(fā)完成。
總結(jié)這篇文章讀完以后相信你一定也實現(xiàn)了一個簡單的 IoC 框架。
雖然說閱讀源碼是了解框架的最終手段。但是 Spring 框架作為一個生產(chǎn)框架,為了保證通用和穩(wěn)定,源碼必定是高度抽象,且處理大量細(xì)節(jié)。所以 Spring 的源碼閱讀起來還是相當(dāng)困難。希望這篇文章能夠幫助理解 Spring Ioc 的實現(xiàn)。
下一篇文章 應(yīng)該會是 《徒手?jǐn)]框架--實現(xiàn)AOP》。
github 地址
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/68342.html
摘要:只實現(xiàn)了基于方法的攔截器。實現(xiàn)了一個遞歸的調(diào)用,直到執(zhí)行完所有的攔截器。目標(biāo)對象攔截器列表這個就是我們框架能夠理解的數(shù)據(jù)結(jié)構(gòu),這個時候問題就變成了對于哪個目標(biāo),增加哪些攔截器。 原文地址:犀利豆的博客 上一講我們講解了Spring 的 IoC 實現(xiàn)。大家可以去我的博客查看點(diǎn)擊鏈接,這一講我們繼續(xù)說說 Spring 的另外一個重要特性 AOP。之前在看過的大部分教程,對于Spring ...
摘要:我們就可以將這些請求合并,達(dá)到一定數(shù)量我們統(tǒng)一提交??偨Y(jié)一個比較生動的例子給大家講解了一些多線程的具體運(yùn)用。學(xué)習(xí)多線程應(yīng)該多思考多動手,才會有比較好的效果。地址徒手?jǐn)]框架系列文章地址徒手?jǐn)]框架實現(xiàn)徒手?jǐn)]框架實現(xiàn) 原文地址:https://www.xilidou.com/2018/01/22/merge-request/ 在高并發(fā)系統(tǒng)中,我們經(jīng)常遇到這樣的需求:系統(tǒng)產(chǎn)生大量的請求,但是這...
摘要:我們繼續(xù)看代碼的意思是這個是一段內(nèi)嵌匯編代碼。也就是在語言中使用匯編代碼。就是匯編版的比較并交換。就是保證在多線程情況下,不阻塞線程的填充和消費(fèi)。微觀上看匯編的是實現(xiàn)操作系統(tǒng)級別的原子操作的基石。 原文地址:https://www.xilidou.com/2018/02/01/java-cas/ CAS 是現(xiàn)代操作系統(tǒng),解決并發(fā)問題的一個重要手段,最近在看 eureka 的源碼的時候。...
摘要:徒手?jǐn)]一個簡單的框架之前在牛逼哄哄的框架,底層到底什么原理得知了遠(yuǎn)程過程調(diào)用簡單來說就是調(diào)用遠(yuǎn)程的服務(wù)就像調(diào)用本地方法一樣,其中用到的知識有序列化和反序列化動態(tài)代理網(wǎng)絡(luò)傳輸動態(tài)加載反射這些知識點(diǎn)。 徒手?jǐn)]一個簡單的RPC框架 之前在牛逼哄哄的 RPC 框架,底層到底什么原理得知了RPC(遠(yuǎn)程過程調(diào)用)簡單來說就是調(diào)用遠(yuǎn)程的服務(wù)就像調(diào)用本地方法一樣,其中用到的知識有序列化和反序列化、動態(tài)...
摘要:先來看代碼吧,一會松哥再慢慢解釋關(guān)于這一段自動配置,解釋如下首先注解表明這是一個配置類。本文的案例,松哥已經(jīng)上傳到上了,地址。我們使用 Spring Boot,基本上都是沉醉在它 Stater 的方便之中。Starter 為我們帶來了眾多的自動化配置,有了這些自動化配置,我們可以不費(fèi)吹灰之力就能搭建一個生產(chǎn)級開發(fā)環(huán)境,有的小伙伴會覺得這個 Starter 好神奇呀!其實 Starter 也都...
閱讀 2141·2023-04-25 17:23
閱讀 2949·2021-11-17 09:33
閱讀 2551·2021-08-21 14:09
閱讀 3669·2019-08-30 15:56
閱讀 2634·2019-08-30 15:54
閱讀 1651·2019-08-30 15:53
閱讀 2160·2019-08-29 13:53
閱讀 1174·2019-08-29 12:31