終於到了激動人心的時刻了,首先感謝小夥伴們的閱讀,若是能多點評論,多點探討就更好了,沒有交流讓我以爲我寫的東西只有標題有點價值,內容只是在浪費你們的時間。爲了淚滴下週能寫下一個框架orm,請小夥伴們能給點信心。前面3篇中介紹的大都是完成某一個層面的工具式的類,看起來就像是人心渙散。緣由就是缺乏一個可以統管這盤散沙的頭頭,那麼這篇內容將會以一個頭頭的角度告訴你們什麼才叫化腐朽爲神奇。java
咱們先回想下spring框架中是否會出現以下相似的代碼呢?spring
ApplicationContext context = new FileSystemXmlApplicationContext(filePath);
這個代碼就是根據xml文件的路徑獲取了spring中的context對象,我習慣叫作上下文對象,根據這個對象咱們就能夠經過調用他的getBean方法根據id來獲取到咱們須要的實例對象了。下面咱們也給咱們的框架來一個相似的對象,固然以前咱們仍然應該將接口實現類的模式進行到底。接口放在新建的包com.tear.ioc.context下面數組
package com.tear.ioc.context; /** * 這是ioc應用容器的接口 * @author rongdi * */ public interface ApplicationContext { /** * 根據id找到bean對應的對象的實例 * @param id * @return */ public Object getBeanInstance(String id); /** * IoC容器中是否包含id爲參數的bean * @param id * @return */ public boolean beanIsExist(String id); /** * 判斷一個bean是否爲單態 * @param name * @return */ public boolean isSingleton(String id); /** * 從容器中得到bean對應的實例, 若是從容器中找不到該bean, 返回null * @param id * @return */ public Object getBeanWithoutCreate(String id); }
爲了使用方便咱們再給他一個抽象類的實現AbstractApplicationContext緩存
package com.tear.ioc.context; import java.lang.reflect.Method; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.dom4j.Document; import org.dom4j.Element; import com.tear.ioc.bean.create.BeanCreator; import com.tear.ioc.bean.create.BeanCreatorImpl; import com.tear.ioc.bean.create.PropertyHandler; import com.tear.ioc.bean.create.PropertyHandlerImpl; import com.tear.ioc.bean.exception.BeanCreateException; import com.tear.ioc.bean.xml.autowire.Autowire; import com.tear.ioc.bean.xml.autowire.ByNameAutowire; import com.tear.ioc.bean.xml.autowire.NoAutowire; import com.tear.ioc.bean.xml.document.DocumentHolder; import com.tear.ioc.bean.xml.document.XmlDocumentHolder; import com.tear.ioc.bean.xml.element.CollectionElement; import com.tear.ioc.bean.xml.element.LeafElement; import com.tear.ioc.bean.xml.element.PropertyElement; import com.tear.ioc.bean.xml.element.RefElement; import com.tear.ioc.bean.xml.element.ValueElement; import com.tear.ioc.bean.xml.element.loader.ElementLoader; import com.tear.ioc.bean.xml.element.loader.ElementLoaderImpl; import com.tear.ioc.bean.xml.element.parser.BeanElementParser; import com.tear.ioc.bean.xml.element.parser.BeanElementParserImpl; /** * 該類繼承ApplicationContext接口,定義成抽象類是由於本類中定義的方法還不夠完善 * 不想本類被直接實例化來使用但願使用它擴展功能後的子類 * @author rongdi * */ public abstract class AbstractApplicationContext implements ApplicationContext { /** * 定義一個文檔持有對象 */ protected DocumentHolder documentHolder = new XmlDocumentHolder(); /** * 定義一個元素加載對象 */ protected ElementLoader elementLoader = new ElementLoaderImpl(); /** * 定義一個Element元素讀取類 */ protected BeanElementParser elementParser = new BeanElementParserImpl(); /** * 定義一個建立bean對象的類 */ protected BeanCreator beanCreator = new BeanCreatorImpl(); /** * 定義一個屬性處理類 */ protected PropertyHandler propertyHandler = new PropertyHandlerImpl(); /** * 定義一個Map用來保存bean元素的id和生成的對應的實例,主要是單實例的bean的對象須要保存起來 */ protected Map<String, Object> beanInstances = new HashMap<String, Object>(); /** * 初始化Elements將對應的document元素中的Element調用elementLoader的方法緩存起來 * 能夠讀取多個xnl文件的路徑,參數爲一個字符串數組 * @param xmlPaths */ protected void initElements(String[] xmlPaths) { try { /** * 獲取當前項目的根路徑 */ URL classPathUrl = AbstractApplicationContext.class.getClassLoader().getResource("."); /** * 爲防止路徑出現漢字亂碼的狀況使用utf-8進行解碼 */ String classPath = java.net.URLDecoder.decode(classPathUrl.getPath(),"utf-8"); /** * 遍歷全部的路徑 */ for (String path : xmlPaths) { /** * 由根路徑加傳入的相對路徑獲取document元素 */ Document doc = documentHolder.getDocument(classPath + path); /** * 將全部的路徑對應的xml文件裏的bean元素都緩存到elementLoader對象中 */ elementLoader.addBeanElements(doc); } } catch (Exception e) { e.printStackTrace(); } } /** * 建立一個bean實例, 若是找不到該bean對應的配置文件的Element對象, 拋出異常 * @param id * @return */ protected Object createBeanInstance(String id) { /** * 首先直接在elementLoader對象中找id對應的元素 */ Element e = elementLoader.getBeanElement(id); /** * 若是沒找到拋出異常 */ if (e == null) throw new BeanCreateException("沒找到 " + id+"對應的bean元素"); /** * 調用本類定義的instance方法實例化該元素對應的類 */ Object result = this.instanceBeanElement(e); System.out.println("建立bean: " + id); System.out.println("該bean的對象是: " + result); /** * 設值注入, 先判斷是否自動裝配 */ Autowire autowire = elementParser.getAutowire(e); if (autowire instanceof ByNameAutowire) { /** * 使用名稱自動裝配 */ autowireByName(result); } else if (autowire instanceof NoAutowire) { /** * 調用設置注入,給根據e元素生成的對象result設置e元素裏配置的property屬性值 */ this.setterInject(result, e); } /** * 返回建立的實例result */ return result; } /** * 實例化一個bean, 若是該bean的配置有constructor-arg元素, 那麼使用帶參數的構造器 * @param e * @return */ protected Object instanceBeanElement(Element e) { /** * 獲得該bean元素的class屬性的值 */ String className = elementParser.getAttribute(e, "class"); /** * 獲得bean節點下面的constructor-arg元素 */ List<Element> constructorElements = elementParser.getConstructorArgsElements(e); /** * 判斷使用什麼構造器進行建立(判斷標準爲bean元素下是否有constructor-arg子元素) * 若是沒有constructor-arg子元素調用無參構造器 */ if (constructorElements.size() == 0) { /** * 沒有constructor-arg子元素, 使用無參構造器 */ return beanCreator.createBeanUseDefaultConstruct(className); } else { /** * 有constructor-arg子元素,獲得全部的構造參數 使用有參數構造器, 構造注入參數 */ List<Object> args = getConstructArgs(e); return beanCreator.createBeanUseDefineConstruct(className, args); } } /** * 建立全部的bean的實例, 延遲加載的不建立 */ protected void createBeanInstances() { /** * 獲取保存到elementLoader對象中的Bean元素 */ Collection<Element> elements = elementLoader.getBeanElements(); /** * 遍歷全部的bean元素 */ for (Element e : elements) { /** * 獲得bean元素的lazy屬性值 */ boolean lazy = elementParser.isLazy(e); /** * 若是不是延遲加載 */ if (!lazy) { /** * 獲得該元素的id屬性值 */ String id = e.attributeValue("id"); /** * 建立這個id所對應的bean的實例 */ Object bean = this.getBeanInstance(id); /** * 若是bean實例進入處理bean的方法中 */ if (bean == null) { /** * 處裏bean的方法分是不是單例兩種狀況進行考慮 */ handleBean(id); } } } } /** * 處理bean, 若是是單態的, 則加到map中, 非單態, 則建立返回 * @param id * @return */ protected Object handleBean(String id) { /** * 首先根據傳入的id屬性值找到該bean建立一個對應的bean的實例 */ Object beanInstance = createBeanInstance(id);; /** * 若是是單例的則保存到Map中方便須要的時候取出 */ if (isSingleton(id)) { /** * 單態的話, 放到map中 */ this.beanInstances.put(id, beanInstance); } /** * 返回建立的bean的實例 */ return beanInstance ; } /** * 判斷id值對應的bean是否爲單態的 */ public boolean isSingleton(String id) { /** * 使用ElementLoader方法得到對應的Element */ Element e = elementLoader.getBeanElement(id); /** * 使用ElementReader判斷是否爲單態 */ return elementParser.isSingleton(e); } /** * 經過property元素爲參數obj設置屬性 * @param obj * @param e */ protected void setterInject(Object obj, Element beanElement) { /** * 返回bean元素的全部的property標籤對應的元素 */ List<PropertyElement> properties = elementParser.getPropertyValue(beanElement); /** * 調用本類定義的方法獲得所須要的屬性名與所要設置的值的對信息 */ Map<String, Object> propertiesMap = this.getPropertyArgs(properties); /** * 將對應的值設置到obj對象中 */ propertyHandler.setProperties(obj, propertiesMap); } /** * 以map的形式獲得須要注入的參數對象, key爲setter方法對應的屬性名, value爲參數對象 * @param properties * @return */ protected Map<String, Object> getPropertyArgs(List<PropertyElement> properties) { /** * 定義一個結果映射保存所須要的屬性名與所要設置的值的對信息 */ Map<String, Object> result = new HashMap<String, Object>(); /** * 遍歷全部的property元素 */ for (PropertyElement p : properties) { /** * 獲得prperty元素中的子元素 */ LeafElement le = p.getLeafElement(); /** * 判斷若是是RefElement元素 */ if (le instanceof RefElement) { /** * 將對應的屬性名和須要設置進去的實例對象保存在map中 */ result.put(p.getName(), this.getBeanInstance((String)le.getValue())); } else if (le instanceof ValueElement) { /** * 若是是ValueElement,將對應的屬性名和須要設置的值保存到Map中 */ result.put(p.getName(), le.getValue()); } else if(le instanceof CollectionElement) { /** * 先判斷是不是CollectionElement若是是再判斷Collection標籤裏面放的是value標籤仍是ref標籤 * 能夠直接取出Collection裏面的List判斷還要判斷類型是list仍是set,根據不一樣狀況調用不一樣的方法 * 將值放入result的map中 */ if(this.childIsValueElement((CollectionElement)le)) { if("list".equals(le.getType())) result.put(p.getName(),this.arrayToArrayList((Object[])le.getValue())); else { result.put(p.getName(),this.arrayToHashSet((Object[])le.getValue())); } } else { if("list".equals(le.getType())){ result.put(p.getName(),this.arrayToArrayList(this.getValuesIfChildIsRefElement(le))); } else { result.put(p.getName(),this.arrayToHashSet(this.getValuesIfChildIsRefElement(le))); } } } } /** * 返回該結果信息 */ return result; } /** * 若是collectionElement下是ref元素那麼調用該方法將集合標籤中全部ref標籤下對應的實例都生成好後 * 返回這些實例的一個Object數組形式 * @param le * @return */ protected Object[] getValuesIfChildIsRefElement(LeafElement le) { /** * 定義一個臨時存放的ArrayList */ List<Object> tempList = new ArrayList<Object>(); /** * 遍歷在CollectionElement裏面取出的Object數組,由於這裏都是ref標籤,根據對應的值獲得實例對象 */ for(Object o:(Object[])le.getValue()) { tempList.add(this.getBeanInstance((String)o)); } return tempList.toArray(); } /** * 將數組轉換爲ArrayList的方法 * @param obj * @return */ protected List<Object> arrayToArrayList(Object[] obj) { List<Object> temp = new ArrayList<Object>(); for(Object o:obj) { temp.add(o); } return temp; } /** * 將數組轉換成HashSet的方法 */ protected Set<Object> arrayToHashSet(Object[] obj) { Set<Object> temp = new HashSet<Object>(); for(Object o:obj) { temp.add(o); } return temp; } /** * 判斷CollectionElement中配置的是ValueElement元素,若是是則返回true */ protected boolean childIsValueElement(CollectionElement ce) { /** * 在CollectionElement元素中獲得保存子元素的list判斷該list中是不是ValueElement元素 * 若是是返回true */ if(ce.getList().get(0) instanceof ValueElement) { return true; } return false; } /** * 獲得一個bean裏面配置的構造參數 * @param e * @return */ protected List<Object> getConstructArgs(Element beanElment) { /** * 獲得該bean元素全部的構造參數元素,該參數可能使RefElement和ValueElement */ List<LeafElement> datas = elementParser.getConstructorValue(beanElment); /** * 定義一個結果信息保存獲得的的參數集合 */ List<Object> result = new ArrayList<Object>(); /** * 遍歷全部的構造參數元素 */ for (LeafElement d : datas) { /** * 若是是ValueElement元素那麼直接將該元素的值保存到結果中 */ if (d instanceof ValueElement) { d = (ValueElement)d; result.add(d.getValue()); } else if (d instanceof RefElement) { d = (RefElement)d; String refId = (String)d.getValue(); /** * 若是是引用元素, 則直接調getBean去獲取(獲取不到則建立),本方法原本就間接的被 * getgetBeanInstance方法調用了,如今在這個方法裏在調用getBeanInstance * 這個方法至關於造成了一個遞歸調用,遞歸的出口在引用所對一個的bean中再沒有引用 */ result.add(this.getBeanInstance(refId)); } } return result; } /** * 自動裝配一個對象, 獲得該bean的全部setter方法, 再從容器中查找對應的bean * 例如, 若是bean中有一個setSchool(School)方法, 那麼就去查名字爲school的bean, * 再調用setSchool方法設入對象中 * @param obj */ protected void autowireByName(Object obj) { /** * 獲得該對象全部的setXXX方法和對應的屬性(bean的id值) */ Map<String, Method> methods = propertyHandler.getSetterMethodsMap(obj); /** * 遍歷全部的方法 */ for (String s : methods.keySet()) { /** * 獲得對應的bean元素 */ Element e = elementLoader.getBeanElement(s); /** * 沒有對應的元素配置, 繼續循環 */ if (e == null) continue; /** * 調用getBeanInstance方法返回beanInstance */ Object beanInstance = this.getBeanInstance(s); /** * 獲得該對象的setter方法 */ Method method = methods.get(s); /** * 將產生的實例調用executeMethod使用反射的方式設值到obj對象中,實現按名字的自動裝配 */ propertyHandler.executeMethod(obj, beanInstance, method); System.out.println("執行"+method.getName()+"方法給對象:"+obj+"注入"+beanInstance); } } /** * 獲得對應id的bean的實例 */ public Object getBeanInstance(String id) { Object beanInstance = this.beanInstances.get(id); /** * 若是獲取不到該bean, 則調用handleBean處理 */ if (beanInstance == null) { /** * 判斷處理單態或者非單態的bean */ beanInstance = handleBean(id); } /** * 返回獲得的bean的實例 */ return beanInstance; } /** * 判斷對應id的bean元素是否存在(直接到配置文件中找不要到elementLoader對象的緩存中找) */ public boolean beanIsExist(String id) { /** * 調用ElementLoader對象, 根據id獲得對應的Element對象 */ Element e = elementLoader.getBeanElement(id); return (e == null) ? false : true; } /** * 在本類的緩存中得到id對應的實例,若果該id對應的bean是單態的就能獲取到 */ public Object getBeanWithoutCreate(String id) { return this.beanInstances.get(id); } }
看到上面這個類是否有人會有點感受了,沒錯這就是簡單的使用了一下外觀模式將以前看似無關的類所有加到這個外觀類之中,下降了被加入類之間的耦合度,這就是爲何以前看起來那些類看起來沒多大關係,就像各自層裏面的工具類的緣由。因爲有了這個外觀類的出現就像一個頭頭統籌了各種資源進行統一的調配。小夥伴們從此也能夠多多用用這個模式,用起來也是很簡單,呵呵。至於詳細的註釋我也是寫的很辛苦了,全在代碼裏面了,多琢磨一下就不難理解了。框架
到了這個層面了,應該作一下收尾了,可能有不少人會奇怪,我靠,你加了這麼個抽象類,我到底要怎麼使用才能達到spring的那種根據上下文context獲取須要的bean的功能呢?別急,立刻就來了。XmlApplicationContext類以下dom
package com.tear.ioc.context; /** * 這是真正使用的IoC的應用框架類,能夠建立全部配置好的類的實例 * @author Administrator * */ public class XmlApplicationContext extends AbstractApplicationContext { public XmlApplicationContext(String[] xmlPaths) { /** * 初始化文檔和元素 */ initElements(xmlPaths); /** * 建立全部bean的實例 */ createBeanInstances(); } }
哈哈,看了上面的代碼是否是很興奮,Ioc終於完成了,並且只須要調用這個構造方法,將全部的xml文件以數組的形式傳入進去就會自動建立全部非延遲加載的bean,並且也會完成依賴注入了。話很少說直接上測試代碼就知道怎麼用了。測試包test下創建com.tear.ioc.context包和com.tear.ioc.object包,object包下準備了各種的測試bean的類,具體淚有點多不貼出來了,能夠直接到後面提供的百度雲中去下載。測試用例以下工具
package com.tear.ioc.context; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.util.ArrayList; import java.util.HashSet; import org.junit.After; import org.junit.Before; import org.junit.Test; import com.tear.ioc.object.XmlApplicationContextObject1; import com.tear.ioc.object.XmlApplicationContextObject2; import com.tear.ioc.object.XmlApplicationContextObject3; import com.tear.ioc.object.XmlApplicationContextObject4; public class XmlApplicationContextTest { ApplicationContext ctx; @Before public void setUp() throws Exception { ctx = new XmlApplicationContext( new String[] { "/resources/context/XmlApplicationContext1.xml" }); } @After public void tearDown() throws Exception { ctx = null; } @Test public void testGetBean() { // 拿到第一個, 使用無參構造器建立 XmlApplicationContextObject1 obj1 = (XmlApplicationContextObject1) ctx .getBeanInstance("test1"); assertNotNull(obj1); } @Test public void testSingleton() { // test1是單態bean XmlApplicationContextObject1 obj1 = (XmlApplicationContextObject1) ctx .getBeanInstance("test1"); XmlApplicationContextObject1 obj2 = (XmlApplicationContextObject1) ctx .getBeanInstance("test1"); assertEquals(obj1, obj2); // test3不是單態bean XmlApplicationContextObject1 obj3 = (XmlApplicationContextObject1) ctx .getBeanInstance("test3"); XmlApplicationContextObject1 obj4 = (XmlApplicationContextObject1) ctx .getBeanInstance("test3"); assertFalse(obj3.equals(obj4)); } @Test public void testConstructInjection() { XmlApplicationContextObject1 obj1 = (XmlApplicationContextObject1) ctx .getBeanInstance("test1"); // 拿到第二個, 使用多參數構造器建立 XmlApplicationContextObject2 obj2 = (XmlApplicationContextObject2) ctx .getBeanInstance("test2"); assertNotNull(obj2); assertEquals(obj2.getName(), "rongdi"); assertEquals(obj2.getAge(), 22); assertEquals(obj2.getObject1(), obj1); } /* * 測試自動裝配 */ @Test public void testAutowire() { XmlApplicationContextObject3 obj1 = (XmlApplicationContextObject3) ctx .getBeanInstance("test4"); assertNotNull(obj1); XmlApplicationContextObject1 obj2 = obj1.getObject1(); System.out.println(obj2); assertNotNull(obj2); XmlApplicationContextObject1 obj3 = (XmlApplicationContextObject1) ctx .getBeanInstance("object1"); assertEquals(obj2, obj3); } /* * 測試是否包含該bean */ @Test public void testContainsBean() { boolean result = ctx.beanIsExist("test1"); assertTrue(result); result = ctx.beanIsExist("test5"); assertTrue(result); result = ctx.beanIsExist("No exists"); assertFalse(result); } /* * 測試延遲加載 */ @Test public void testLazyInit() { // test5是延遲加載的, 沒有調用過getBean方法, 那麼容器中就不會建立這個bean Object obj = ctx.getBeanWithoutCreate("test5"); assertNull(obj); // System.out.println(obj); obj = ctx.getBeanInstance("test5"); assertNotNull(obj); System.out.println(obj); } /* * 測試設值注入 */ @Test public void testSetProperties() { XmlApplicationContextObject3 obj1 = (XmlApplicationContextObject3) ctx .getBeanInstance("test6"); XmlApplicationContextObject1 obj2 = (XmlApplicationContextObject1) ctx .getBeanInstance("object1"); assertEquals(obj1.getName(), "rongdi"); assertEquals(obj1.getAge(), 22); assertEquals(obj1.getObject1(), obj2); XmlApplicationContextObject4 obj4 = (XmlApplicationContextObject4) ctx .getBeanInstance("test7"); System.out.println((ArrayList<Object>) obj4.getList()); System.out.println((HashSet<Object>) obj4.getSet()); System.out.println((ArrayList<Object>) obj4.getRefTest()); } }
從上面測試用例的方法中咱們就能夠看到咱們的Ioc框架的完整使用方法了。相信小夥伴當心琢磨下,人人都能寫出本身的Ioc了,固然該Ioc框架功能還不是很完善,,好比spring的Ioc中能夠按照類型注入和名稱注入,咱們只實現了按名稱注入,小夥伴們須要本身實現一個AutoWire接口改一下代碼不難實現。至於最近一直都很流行的註解配置的方式,我給小夥伴們提一個本身的實現思路,相信大多數人都能實現。註解方式和xml配置方式的區別之處只在於loader層和parser層,咱們能夠仿照目前的流程寫一個註解的loader層和一個註解的parser層而後在咱們AbstractApplicationContext類中再集成進入這兩個類,而後改下AbstractApplicationContext中的方法就能實現,這也算是外觀模式的一個好處了。若是說小夥伴們的呼聲比較高,我會考慮在這周抽時間寫下註解的實現部分,否則我就認爲你們都能本身完成。就會考慮去寫下一個框架orm了。再次重申,淚滴分享的項目並不是是要取代現存的已經通過時間檢驗過的完善的框架,只爲了讓你們更加深刻的理解和使用現有的項目,因此代碼在性能方面沒有花時間做過多的考慮。性能
好了,一如既往的屌絲專用百度雲源碼地址http://pan.baidu.com/s/1bn70g2N測試