終於到了激動人心的時刻了,首先感謝小伙伴們的閱讀,如果能多點評論,多點探討就更好了,沒有交流讓我覺得我寫的東西只有標題有點價值,內容只是在浪費大家的時間。為了淚滴下周能寫下一個框架orm,請小伙伴們能給點信心。前面3篇中介紹的大都是完成某一個層面的工具式的類,看起來就像是一盤散沙。原因就是缺少一個能夠統管這盤散沙的頭頭,那么這篇內容將會以一個頭頭的角度告訴大家什么才叫化腐朽為神奇。
我們先回想下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類如下
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