剛寫博客瀏覽量第一天就有1000多人次,給了我很大的鼓舞決定熬夜再寫一篇。對於前兩篇來講無非就是使用dtd驗證xml,而後解析xml,和IOC的核心仍是差的很遠,相信不少小夥伴們都感受看得不過癮了,這期咱們就進入正題了。java
先說說上期有個小夥伴提意見讓我把IocUtil類使用反射不要用那麼多if-else當時以爲頗有道理,可是回來仔細想了下,通常數據類型仍是要和其餘類型分開否則無法處理,IocUtil代碼再次貼上若是有高手以爲能夠改動,能夠再次給我意見,再次謝謝那位給意見的小夥伴。spring
package com.tear.ioc.util; /** * 這是一個幫助類 */ public class IocUtil { /** * 若是該類型是java中的幾個基本數據類型那麼返回它的類型,注意Integer.type就是得到他的class對象 * 若是不是基礎類型則使用getClass()返回它的Class對象 * @param obj * @return */ public static Class<?> getClass(Object obj) { if (obj instanceof Integer) { return Integer.TYPE; } else if (obj instanceof Boolean) { return Boolean.TYPE; } else if (obj instanceof Long) { return Long.TYPE; } else if (obj instanceof Short) { return Short.TYPE; } else if (obj instanceof Double) { return Double.TYPE; } else if (obj instanceof Float) { return Float.TYPE; } else if (obj instanceof Character) { return Character.TYPE; } else if (obj instanceof Byte) { return Byte.TYPE; } return obj.getClass(); }/** * 判斷className的類型是否爲基礎類型。如java.lang.Integer, 是的話將數據進行轉換 * 成對應的類型該方法是供本類中的方法調用的,做用是根據type類型的值將對應的value數據轉換 * 成對應的type類型的值 * @param className * @param data * @return */ public static Object getValue(String className, String data) { /** * 下面的全部if和else if都是判斷是不是java的8中基本數據類型的包裝類型 */ if (isType(className, "Integer")) { return Integer.parseInt(data); } else if (isType(className, "Boolean")) { return Boolean.valueOf(data); } else if (isType(className, "Long")) { return Long.valueOf(data); } else if (isType(className, "Short")) { return Short.valueOf(data); } else if (isType(className, "Double")) { return Double.valueOf(data); } else if (isType(className, "Float")) { return Float.valueOf(data); } else if (isType(className, "Character")) { /** * 若是是Character類型則取第一個字符 */ return data.charAt(0); } else if (isType(className, "Byte")) { return Byte.valueOf(data); } else { /** * 若是不是8種基本數據類型的包裝類那麼就是自定義的類了,直接返回該值 */ return data; } } /** * 該方法是判斷類名中是否含有對應的type字符串的方法,如判斷className:java.lang.Integer中 * 是否包含Integer這樣就返回true,不包含則返回false,該方法是供上面的方法調用的 * @param className * @param type * @return */ private static boolean isType(String className, String type) { if (className.lastIndexOf(type) != -1) return true; return false; } }
前兩期已經把將Ioc所須要用到的配置文件xml從dtd驗證到加載內存到解析一整套流程介紹完了。這期咱們應該處理從xml解析出來的各個bean元素了。因爲Ioc的就是給你生成對象,生成對象不論是普通的new仍是使用反射,無非就是直接或者間接的使用無參數的構造方法或者是有參數的構造方法,。咱們建立一個包com.tear.ioc.bean.create而後在這個包下定義一個生成對象的接口BeanCreator數組
package com.tear.ioc.bean.create; import java.util.List; /** * 這是一個建立bean的接口 * @author rongdi * */ public interface BeanCreator { /** * 使用無參的構造器建立bean實例, 不設置任何屬性 * @param className * @return */ public Object createBeanUseDefaultConstruct(String className); /** * 使用有參數的構造器建立bean實例, 不設置任何屬性 * @param className * @param args 參數集合 * @return */ public Object createBeanUseDefineConstruct(String className, List<Object> args); }
上面接口實際上傳入的className字符串就是從xml中解析出來的配置的類的全名,至於args就是解析出的全部constructor-arg標籤下的值組裝出來的集合。關鍵部分就是看這個接口的實現類了。實現類BeanCreatorImpl以下框架
package com.tear.ioc.bean.create; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.List; import com.tear.ioc.bean.exception.BeanCreateException; import com.tear.ioc.util.IocUtil; /** * 這是一個使用構造方法建立bean對應的實例的類 * @author rongdi * */ public class BeanCreatorImpl implements BeanCreator { /** * 使用默認的構造方法建立實例,傳入的參數爲類的全名,能夠從bean的class屬性的值那裏得到 * 再經過反射建立實例 */ @Override public Object createBeanUseDefaultConstruct(String className) { try { /** * 得到類的全名對應的Class對象 */ Class<?> clazz = Class.forName(className); /** * 使用反射的方式返回一個該類的實例,使用的是無參數的構造方法 */ return clazz.newInstance(); } catch (ClassNotFoundException e) { throw new BeanCreateException("沒有找到"+className+"該類 " + e.getMessage()); } catch (Exception e) { throw new BeanCreateException(e.getMessage()); } } @Override public Object createBeanUseDefineConstruct(String className, List<Object> args) { /** * 將傳入的List<Object>類型的參數轉換成Class數組的形式 */ Class<?>[] argsClass = this.getArgsClasses(args); try { /** * 得到傳入類的全名的Class對象 */ Class<?> clazz = Class.forName(className); /** * 經過反射獲得該類的構造方法(Constructor)對象 */ Constructor<?> constructor = getConstructor(clazz, argsClass); /** * 根據參數動態建立一個該類的實例 */ return constructor.newInstance(args.toArray()); } catch (ClassNotFoundException e) { e.printStackTrace(); throw new BeanCreateException(className+"類沒有找到 " + e.getMessage()); } catch (NoSuchMethodException e) { e.printStackTrace(); throw new BeanCreateException("沒找到"+className+"中對應的構造方法" + e.getMessage()); } catch (Exception e) { e.printStackTrace(); throw new BeanCreateException(e.getMessage()); } } /** * 根據類的Class對象和參數的Class對象的列表查找一個類的構造器,注意通常咱們定義方法的時候 * 因爲爲了使用多態原理通常咱們將方法裏的參數定義成咱們想接受的參數的一個父類或者是父接口,這樣咱們 * 想經過該方法就得到不到Constructor對象了,因此該方法只是一個初步的方法還須要進行封裝才能 * 達到咱們想要的效果 * @param clazz 類型 * @param argsClass 構造參數 * @return */ private Constructor<?> getProcessConstructor(Class<?> clazz, Class<?>[] argsClass) { try { Constructor<?> constructor = clazz.getConstructor(argsClass); return constructor; } catch (NoSuchMethodException e) { return null; } } /** * 這個方法纔是真正或獲得構造方法的 * @param clazz * @param argsClass * @return * @throws NoSuchMethodException */ private Constructor<?> getConstructor(Class<?> clazz, Class<?>[] argsClass) throws NoSuchMethodException { /** * 首先調用得到直接根據類名和參數的class列表的構造方法,若是該構造方法要傳入的類不是構造方法 * 中聲明的類,通常須要注入的類都是它的子類,這樣該方法得到的構造方法對象就是null的,這樣 * 就須要在得到全部的構造方法,判斷傳入的是不是構造方法形式參數的實例對象了 */ Constructor<?> constructor = getProcessConstructor(clazz, argsClass); /** * 若是得到的構造方法對象爲空 */ if (constructor == null) { /** * 獲得該類的全部的public的構造器對象 */ Constructor<?>[] constructors = clazz.getConstructors(); /** * 遍歷全部的構造器對象 */ for (Constructor<?> c : constructors) { /** * 獲取到該構造器的全部參數的class對象的Class數組形式 */ Class<?>[] tempClass = c.getParameterTypes(); /** * 判斷該構造器的參數個數是否與argsClass(傳進來)的參數個數相同 */ if (tempClass.length == argsClass.length) { if (isSameArgs(argsClass, tempClass)) { return c; } } } } else { /** * 若是傳入的恰好是構造器中定義的那個類,就會之金額找到該構造器,那麼直接返回該構造器 */ return constructor; } /** * 若是到這裏還沒返回表示沒有找到合適的構造器,直接拋出錯誤 */ throw new NoSuchMethodException("找不到指定的構造器"); } /** * 判斷兩個參數數組類型是否匹配 * @param argsClass * @param constructorArgsCLass * @return */ private boolean isSameArgs(Class<?>[] argsClass, Class<?>[] tempClass) { /** * for循環比較每個參數是否都相同(子類和父類當作相同) */ for (int i = 0; i < argsClass.length; i++) { try { /** * 將傳入參數(前面的參數)與構造器參數,後面的參數進行強制轉換,若是轉換成功說明前面的參數 * 是後面的參數的 子類,那麼能夠認爲類型相同了,若果不是子類就會拋異常 */ argsClass[i].asSubclass(tempClass[i]); /** * 循環到最後一個參數都成功轉換表示類型相同,該構造器合適了 */ if (i == (argsClass.length - 1)) { return true; } } catch (Exception e) { /** * 若是有一個參數類型不符合, 跳出該循環 */ break; } } return false; } /** * 獲得參數集合的class數組 * @param args * @return */ private Class<?>[] getArgsClasses(List<Object> args) { /** * 定義一個集合保存所需參數 */ List<Class<?>> result = new ArrayList<Class<?>>(); /** * 循環全部傳入參數 */ for (Object arg : args) { /** * 將參數轉換成對應的Class對象後加到定義的集合中來 */ result.add(IocUtil.getClass(arg)); } /** * 根據該集合的長度建立一個相同長度的Class數組 */ Class<?>[] a = new Class[result.size()]; /** * 返回集合對應的Class數組 */ return result.toArray(a); } }
實現類中那麼多的代碼基本都是私有的,只是爲了完成接口的兩個實現類,總的來講實現類的做用主要是用來解決下面xml片斷狀況下的對象的生成dom
<bean id="test3" class="com.tear.Test3"></bean> <bean id="test4" class="com.tear.Test4"> <constructor-arg> <value type="java.lang.String">zhangsan</value> </constructor-arg> <constructor-arg> <value type="java.lang.String">123456</value> </constructor-arg> </bean> <bean id="test5" class="com.tear.Test5"> <constructor-arg> <ref bean="test3"/> </constructor-arg> <constructor-arg> <ref bean="test3"/> </constructor-arg> </bean>
細心的小夥伴可能發現了一個很總要的問題,那麼下面的片斷怎麼去生成對象呢?ide
<bean id="test1" class="com.rongdi.Test1"></bean> <bean id="test2" class="com.rongdi.Test2"></bean> <bean id="test3" class="com.tear.Test3"> <property name="aa"> <ref bean="test1"/> </property> <property name="bb"> <ref bean="test2"/> </property> </bean>
若是說構造方法注入屬性叫作構造注入那麼這種就是設值注入了,很容易想到的就是先生成一個對象而後再調用該對象的相應的set方法去將參數set進去。爲了將接口實現類的方式進行到底咱們再次在com.rongdi.ioc.beans.create包下定義一個處理PropertyHandler接口,該接口及其實現類主要是負責完成對一個已有對象進行設值處理。能夠很簡單的想象,可能就須要一個方法,該方法兩個參數一個參數就是須要設值的對象,第二個參數就是該對象須要設置的值,由於值可能有多個,每一個值到底設置到哪裏,因此咱們須要一個map類型的參數;可能還須要一個方法就是獲取該須要設置值的對象所在類裏面的全部setXX方法。工具
package com.tear.ioc.bean.create; import java.lang.reflect.Method; import java.util.Map; /** * 處理屬性的接口 * @author rongdi * */ public interface PropertyHandler { /** * 爲對象obj設置屬性,第一個參數是須要設置值的對象,第二個參數是給Object裏面的變量 * 設什麼值,Map的key就是property元素的name屬性對應於對象的成員變量名,Map的value * 就是對應的值 * @param obj * @param properties 屬性集合 * @return */ public Object setProperties(Object obj, Map<String, Object> properties); /** * 返回一個對象裏面全部的setter方法, 封裝成map, key爲setter方法名去掉set後的字符串 * 至於爲何是這樣的,具體緣由在實現類中的私有方法getMethodNameWithOutSet已經作了 * 詳細的解釋 * @param obj * @return */ public Map<String, Method> getSetterMethodsMap(Object obj); /** * 使用反射執行一個方法,主要是來完成調用一次就爲對象設置一個屬性 * @param object 須要執行方法的對象 * @param argBean 參數的bean * @param method setXX方法對象 */ public void executeMethod(Object object, Object argBean, Method method); }
具體實現類PropertyHandlerImpl以下測試
package com.tear.ioc.bean.create; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import com.tear.ioc.bean.exception.BeanCreateException; import com.tear.ioc.bean.exception.PropertyException; import com.tear.ioc.util.IocUtil; /** * 這是處理屬性的類 * @author rongdi * */ public class PropertyHandlerImpl implements PropertyHandler { /** * 爲對象obj設置屬性,第一個參數是須要設置值的對象,第二個參數是給Object裏面的變量 * 設什麼值,Map的key就是property元素的name屬性對應於對象的成員變量名,Map的value * 就是對應的值 */ @Override public Object setProperties(Object obj, Map<String, Object> properties) { /** * 獲得須要設置的對象obj的Class對象 */ Class<?> clazz = obj.getClass(); try { /** * 遍歷Map中全部的key值,該key值就是對象中須要使用setXXX方法設值的成員變量 */ for (String key : properties.keySet()) { /** * 調用本類中定義的getSetterMethodName方法得到一個屬性的成員變量 * 對應的set方法 */ String setterName = this.getSetterMethodName(key); /** * 得到要給該成員變量設置的值的Class對象 */ Class<?> argClass = IocUtil.getClass(properties.get(key)); /** * 經過反射找到obj對象對應的setXXX方法的Method對象 */ Method setterMethod = getSetterMethod(clazz, setterName, argClass); /** * 經過反射調用該setXXX方法,傳入Map中保存的對應的值 */ setterMethod.invoke(obj, properties.get(key)); } return obj; } catch (NoSuchMethodException e) { throw new PropertyException("對應的setter方法沒找到" + e.getMessage()); } catch (IllegalArgumentException e) { throw new PropertyException("wrong argument " + e.getMessage()); } catch (Exception e) { throw new PropertyException(e.getMessage()); } } /** * 返回一個屬性的setter方法 * @param propertyName * @return */ private String getSetterMethodName(String propertyName) { return "set" + this.firstWordToUpperCase(propertyName); } /** * 將參數s的首字母變爲大寫 * @param key * @return */ private String firstWordToUpperCase(String s) { String firstWord = s.substring(0, 1); String upperCaseWord = firstWord.toUpperCase(); return s.replaceFirst(firstWord, upperCaseWord); } /** * 經過反射獲得methodName對應的Method對象,第一個參數爲操做對象的Class對象 * 第二個參數爲須要操做的方法名,第三個是須要操做的方法的參數的Class列表 * @param objClass * @param methodName * @param argClass * @return * @throws NoSuchMethodException */ private Method getSetterMethod(Class<?> objClass, String methodName, Class<?> argClass) throws NoSuchMethodException { /** * 使用原類型得到方法,也就是不算它的父類或者是父接口。 若是沒有找到該方法, 則獲得null */ Method argClassMethod = this.getMethod(objClass, methodName, argClass); /** * 若是找不到原類型的方法, 則找該類型所實現的接口 */ if (argClassMethod == null) { /** * 調用本類定義的getMethods方法獲得全部名字爲methodName的而且只有一個參數的方法 */ List<Method> methods = this.getMethods(objClass, methodName); /** * 調用本類定義的findMethod方法找到所須要的Method對象 */ Method method = this.findMethod(argClass, methods); if (method == null) { /** * 找不到任何方法直接拋異常 */ throw new NoSuchMethodException(methodName); } /** * 方法不爲空說明找到方法,返回該方法對象 */ return method; } else { /** * 找到了原參數類型的方法直接返回 */ return argClassMethod; } } /** * 根據方法名和參數類型獲得方法, 若是沒有該方法返回null * @param objClass * @param methodName * @param argClass * @return */ private Method getMethod(Class<?> objClass, String methodName, Class<?> argClass) { try { Method method = objClass.getMethod(methodName, argClass); return method; } catch (NoSuchMethodException e) { return null; } } /** * 獲得全部名字爲methodName而且只有一個參數的方法 * @param objClass * @param methodName * @return */ private List<Method> getMethods(Class<?> objClass, String methodName) { /** * 建立一個ArrayList集合用來保存所須要的Method對象 */ List<Method> result = new ArrayList<Method>(); /** * 經過反射獲得全部的方法後遍歷全部的方法 */ for (Method m : objClass.getMethods()) { /** * 若是方法名相同 */ if (m.getName().equals(methodName)) { /** * 獲得方法的全部參數, 若是隻有一個參數, 則添加到集合中 */ Class<?>[] c = m.getParameterTypes(); /** * 若是隻有一個參數就加到ArrayList中 */ if (c.length == 1) { result.add(m); } } } /** * 返回所須要的集合 */ return result; } /** * 方法集合中尋找參數類型是interfaces其中一個的方法 * @param argClass 參數類型 * @param methods 方法集合 * @return */ private Method findMethod(Class<?> argClass, List<Method> methods) { /** * 遍歷全部找到的方法 */ for (Method m : methods) { /** * 判斷參數類型與方法的參數類型是否一致,若是一致說明找到了對應的方法。返回該方法 */ if (this.isMethodArgs(m, argClass)) { return m; } } /** * 沒找到該方法返回null */ return null; } /** * 獲得obj對象中的全部setXXX方法的Map映射,Map的key值爲對應的屬性名,value爲對應的set * 方法的Method對象 */ public Map<String, Method> getSetterMethodsMap(Object obj) { /** * 調用本類中所定義的getSetterMethodsList方法獲得全部的setXXX方法 */ List<Method> methods = this.getSetterMethodsList(obj); /** * 定義一個結果的映射用來存放方法的屬性名(對應到bean裏面就是bean的id屬性的值) * 與對應的set方法 */ Map<String, Method> result = new HashMap<String, Method>(); /** * 遍歷全部的Method對象,調用本類的getMethodNameWithoutSet獲得該方法 * 對應的屬性名(也就是去掉set後的值) */ for (Method m : methods) { String propertyName = this.getMethodNameWithOutSet(m.getName()); /** * 將所需的屬性名和方法對信息放入map中 */ result.put(propertyName, m); } /** * 返回所需的map */ return result; } /** * 將setter方法還原, setName做爲參數, 獲得name * @param methodName * @return */ private String getMethodNameWithOutSet(String methodName) { /** * 獲得方法名中去掉set以後的名字,爲何是這樣的,咱們不得不說下在設值注入的時候實際上 * 到底注入什麼參數其實是看setXxx方法去掉set而後再把後面的第一個字符變成小寫以後的xxx * 做爲依據如一個類中有屬性 * private String yy; * public void setXx(String aa) { * this.yy = aa; * } * <bean id="test3" class="com.tear.Test3"> * <property name="xx"> * <value type="java.lang.String">123456</value> * </property> * </bean> * 實際上這裏的property標籤的name值要注入的地方的依據並非去找類中屬性名爲xx的去設置 * 而是去找xx的第一個字符大寫前面加上set即setXx方法來完成設值,因此看到xx屬性實際上會 * 注入到了yy成員變量中,因此這裏的配置文件的屬性的值的注入始終是找該屬性變成相應的set方法 * 去設值的,這一點無論在struts2的action層仍是在spring的Ioc都有很明顯的表現,不相信的 * 小夥伴能夠本身去試試。固然做爲本身的實現你能夠本身定義設值的規則 */ String propertyName = methodName.substring(3); /** * 獲得該屬性名的第一個大寫字母 */ String firstWord = propertyName.substring(0, 1); /** * 將大寫字母換成小寫的 */ String lowerFirstWord = firstWord.toLowerCase(); /** * 返回該setXXX方法對應的正確的屬性名 */ return propertyName.replaceFirst(firstWord, lowerFirstWord); } /** * 經過反射獲得obj對象中的全部setXXX方法對應的Method對象的集合形式 * @param obj * @return */ private List<Method> getSetterMethodsList(Object obj) { /** * 反射的入口,首先獲得obj對象的Class對象 */ Class<?> clazz = obj.getClass(); /** * 由該對象的Class對象得打全部的方法 */ Method[] methods = clazz.getMethods(); /** * 聲明一個結果集合,準備用來方法所須要的Method對象 */ List<Method> result = new ArrayList<Method>(); /** * 遍歷全部獲得的Method對象找到set開頭的方法將其放到結果集合中 */ for (Method m : methods) { if (m.getName().startsWith("set")) { result.add(m); } } /** * 返回所須要的Method對象的結果集合 */ return result; } /** * 執行某一個方法,該方法用來被自動裝配的時候調用對應對象中的setter方法把產生的argBean對象set進去的方法 * 其中object爲帶設值的對象,argBean就是要設進去的參數,第三個參數就是setXX對象自己的Method對象,主要是 * 方便使用反射 */ public void executeMethod(Object object, Object argBean, Method method) { try { /** * 獲取須要調用的方法的參數類型 */ Class<?>[] parameterTypes = method.getParameterTypes(); /** * 若是參數數量爲1,則執行該方法,由於做爲setXX方法參數個數確定 * 是1 */ if (parameterTypes.length == 1) { /** * 若是參數類型同樣, 才執行方法 */ if (isMethodArgs(method, parameterTypes[0])) { method.invoke(object, argBean); } } } catch (Exception e) { /** * 由於如該方法主要是被自動裝備的方法調用,因此若是遇到問題拋出自動裝配異常的信息 */ throw new BeanCreateException("自動裝配異常 " + e.getMessage()); } } /** * 判斷參數類型(argClass)是不是該方法(m)的參數類型 * @param m * @param argClass * @return */ private boolean isMethodArgs(Method m, Class<?> argClass) { /** * 獲得方法的參數類型 */ Class<?>[] c = m.getParameterTypes(); /** * 若是隻有一個參數才符合要求 */ if (c.length == 1) { try { /** * 將參數類型(argClass)與方法中的參數類型進行強制轉換, 不拋異常說明 * 傳入的參數是方法參數的子類的類型,或者就是方法參數的類型。 */ argClass.asSubclass(c[0]); /** * 沒拋異常返回true,其餘情返回false */ return true; } catch (ClassCastException e) { return false; } } return false; } }
詳細註釋見代碼。以上就實現了設值注入的方法。這一期因爲時間關係測試代碼就不寫了。this
有想法的小夥伴們可能就會發現,從第一期的document層提供dtd對xml的綁定校驗,到loader層提供將document以dom4j中的Element的形式加載到內存的方法,再到parser層中對內存中的全部Element的針對各類標籤的解析方法,再到create層的針對類的全名及構造參數建立類的對象的方法,針對生成的類依據property屬性的設值注入的方法。看上去說了這麼就徹底就是一個個的工具類而已,最多能夠算一個從底下一層層上來的工具而已,對於整個Ioc來講半點功能都沒有實現,這有什麼用呢?哈哈這就是本項目設計的巧妙之處,後面只要稍做處理就能化腐朽爲神奇,使看起來散亂的工具類一下拼接成完成的框架。小夥伴們也能夠趁機多考慮下淚滴會怎麼實現呢?若是是大家大家會怎麼實現呢?固然要知道我怎麼實現的,敬請期待下一期,哈哈,下期再見。屌絲專用百度雲代碼地址http://pan.baidu.com/s/1gxNwaspa