剛寫博客瀏覽量第一天就有1000多人次,給了我很大的鼓舞決定熬夜再寫一篇。對於前兩篇來說無非就是使用dtd驗證xml,然后解析xml,和IOC的核心還是差的很遠,相信很多小伙伴們都感覺看得不過癮了,這期我們就進入正題了。
先說說上期有個小伙伴提意見讓我把IocUtil類使用反射不要用那么多if-else當時覺得很有道理,但是回來仔細想了下,一般數據類型還是要和其他類型分開不然沒法處理,IocUtil代碼再次貼上如果有高手覺得可以改動,可以再次給我意見,再次謝謝那位給意見的小伙伴。
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片段情況下的對象的生成
<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>
細心的小伙伴可能發現了一個很總要的問題,那么下面的片段怎么去生成對象呢?
<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; } }
詳細注釋見代碼。以上就實現了設值注入的方法。這一期由於時間關系測試代碼就不寫了。
有想法的小伙伴們可能就會發現,從第一期的document層提供dtd對xml的綁定校驗,到loader層提供將document以dom4j中的Element的形式加載到內存的方法,再到parser層中對內存中的所有Element的針對各種標簽的解析方法,再到create層的針對類的全名及構造參數創建類的對象的方法,針對生成的類依據property屬性的設值注入的方法。看上去說了這么就完全就是一個個的工具類而已,最多可以算一個從底下一層層上來的工具而已,對於整個Ioc來說半點功能都沒有實現,這有什么用呢?哈哈這就是本項目設計的巧妙之處,后面只要稍作處理就能化腐朽為神奇,使看起來散亂的工具類一下拼接成完成的框架。小伙伴們也可以趁機多考慮下淚滴會怎么實現呢?如果是你們你們會怎么實現呢?當然要知道我怎么實現的,敬請期待下一期,哈哈,下期再見。屌絲專用百度雲代碼地址http://pan.baidu.com/s/1gxNwa