自己寫一個java的mvc框架吧(三)
自己寫一個mvc框架吧(三)
根據Method獲取引數並轉換引數型別
上一篇我們將url與Method的對映建立完畢,併成功的將對映關係建立起來了。這一篇我們將根據Method的入參引數名稱、引數型別來獲取引數 ,並轉換引數型別 ,使其能夠符合Method的定義 。
事先說明
因為這裡只是一個mvc框架的簡單實現,僅僅只做到了基本資料型別 和基本資料型別包裝類 的轉換,沒有做到spring那樣的很複雜的資料繫結功能。所以我在程式碼上面加了比較強的校驗。
現在開始寫吧
我們從一次http請求中 獲取引數的時候,一般需要知道引數的名稱,引數名稱我們可以用方法的入參名稱。這一步我們已經做好了(可以看上一篇:https://www.cnblogs.com/hebaibai/p/10340884.html )。
在這裡我們需要定義一個方法,用來從請求中的String型別 的引數轉換成為我們定義的Method的入參型別 。至於為啥請求中獲取的引數是String型別的可以檢視一下ServletRequest.java 中的方法定義。這裡就不講啦~。所以我們這個方法可以是這樣的:
/** * 獲取方法的入參 * * @param valueTypes * @param valueNames * @param valueSource * @return */ public Object[] getMethodValue(Class[] valueTypes, String[] valueNames, Map<String, String[]> valueSource){ 。。。 }
這裡接受三個引數
1:valueTypes 這個是Method的入參型別
2:valueNames 這個是Method的入參引數名稱
3:valueSource 這個是一次請求中所有的引數。這個引數是一個Map。value的泛型是一個String陣列,這裡用陣列的原因是因為在一次請求中,名稱相同的引數可能會有多個。可以檢視ServletRequest.java 中的方法:
public String[] getParameterValues(String name);
說明一下
這裡我依然不寫Servlet ,因為還不到時候,我們可以在整個程式碼的架子都寫起來之後,每一部分都經過單元測試之後,最後再寫這個入口。就像搭積木一樣,先把每一塊都準備好,最後將所有的拼起來就好了。
繼續
現在這個獲取方法請求入參的方法定義完了,接下來怎麼樣根據引數型別將String型別的引數轉換出來是一個麻煩的事情,這裡要寫好多if else 的程式碼。我們一步一步的寫,先寫一個基本資料型別轉換的。
資料型別轉換
這裡定義一個介面ValueConverter.java ,裡面只有一個方法,用於做資料轉換
<T> T converter(String[] value, Class<T> valueClass);
有同學要問了,這裡為啥要定義成一個介面呢?為啥不直接寫一個Class,裡面直接寫實現程式碼呢?
因為我這裡還有一個工廠類要用來獲取ValueConverter.java 的實現呀!工廠類的程式碼張這個樣子
/** * 資料轉換器工廠類 */ public class ValueConverterFactory { /** * 根據目標型別獲取轉換器 * * @param valueClass * @return */ public static ValueConverter getValueConverter(Class valueClass) { if(...){ return ValueConverter; } throw new UnsupportedOperationException("資料型別:" + valueClass.getName() + " 不支援轉換!"); } }
為啥要寫這個工廠類呢?還要從介面ValueConverter.java 說起,java中的介面(interface )並不是 為了在開發中寫一個service或者寫一個DAO讓程式碼好看而定義的,而是讓我們定義標準的。規定在這個標準中每個方法的入參、出參、異常資訊、方法名稱以及這個方法是用來做什麼的。只要是這個介面的實現類,就必須要遵守這個標準。呼叫者在呼叫的時候也不需要知道它呼叫的是哪一個實現類,只要按照介面標準進行傳參,就可以拿到想要的出參。
所以我們在使用這一段程式碼的時候只需要給ValueConverterFactory 傳如一個Class ,工廠類返回一個可以轉換這個Class的實現就好了。
將上面的getMethodValue 補充完畢就是這個樣子:
/** * 獲取方法的入參 * * @param valueTypes * @param valueNames * @param valueSource * @return */ public Object[] getMethodValue(Class[] valueTypes, String[] valueNames, Map<String, String[]> valueSource) { Assert.notNull(valueTypes); Assert.notNull(valueNames); Assert.notNull(valueSource); Assert.isTrue(valueNames.length == valueTypes.length, "getMethodValue() 引數長度不一致!"); int length = valueNames.length; Object[] values = new Object[length]; for (int i = 0; i < values.length; i++) { Class valueType = valueTypes[i]; String valueName = valueNames[i]; String[] strValues = valueSource.get(valueName); //來源引數中 key不存在或者key的值不存在,設定值為null if (strValues == null) { values[i] = null; continue; } ValueConverter valueConverter = ValueConverterFactory.getValueConverter(valueType); Object converter = valueConverter.converter(strValues, valueType); values[i] = converter; } return values; }
在這裡就可以看到,我們在呼叫工廠類的getValueConverter 方法,工廠類就會給我們一個轉換器ValueConverter ,我們只需要用它來進行轉換就好了,不需要知道是怎麼轉換的。
但是我們還是要先寫幾個轉換器,因為現在並沒有真正可用的轉換器,有的只是標準。現在我們先寫一個基本資料型別的轉換器。
基本資料型別的轉換
在這裡,我們先要通過Class判斷一下它是不是一個基本型別,注意:
這裡我說的基本資料型別是指 java中的 基本資料型別 和 它們的包裝類 以及 String
先寫一個工具類:
public class ClassUtils { /** * java 基本型別 */ public static List<Class> JAVA_BASE_TYPE_LIST = new ArrayList<>(); public final static Class INT_CLASS = int.class; public final static Class LONG_CLASS = long.class; public final static Class FLOAT_CLASS = float.class; public final static Class DOUBLE_CLASS = double.class; public final static Class SHORT_CLASS = short.class; public final static Class BYTE_CLASS = byte.class; public final static Class BOOLEAN_CLASS = boolean.class; public final static Class CHAR_CLASS = char.class; public final static Class STRING_CLASS = String.class; public final static Class INT_WRAP_CLASS = Integer.class; public final static Class LONG_WRAP_CLASS = Long.class; public final static Class FLOAT_WRAP_CLASS = Float.class; public final static Class DOUBLE_WRAP_CLASS = Double.class; public final static Class SHORT_WRAP_CLASS = Short.class; public final static Class BOOLEAN_WRAP_CLASS = Boolean.class; public final static Class BYTE_WRAP_CLASS = Byte.class; public final static Class CHAR_WRAP_CLASS = Character.class; static { //基本資料型別 JAVA_BASE_TYPE_LIST.add(INT_CLASS); JAVA_BASE_TYPE_LIST.add(LONG_CLASS); JAVA_BASE_TYPE_LIST.add(FLOAT_CLASS); JAVA_BASE_TYPE_LIST.add(DOUBLE_CLASS); JAVA_BASE_TYPE_LIST.add(SHORT_CLASS); JAVA_BASE_TYPE_LIST.add(BYTE_CLASS); JAVA_BASE_TYPE_LIST.add(BOOLEAN_CLASS); JAVA_BASE_TYPE_LIST.add(CHAR_CLASS); //基本資料型別(物件) JAVA_BASE_TYPE_LIST.add(STRING_CLASS); JAVA_BASE_TYPE_LIST.add(INT_WRAP_CLASS); JAVA_BASE_TYPE_LIST.add(LONG_WRAP_CLASS); JAVA_BASE_TYPE_LIST.add(FLOAT_WRAP_CLASS); JAVA_BASE_TYPE_LIST.add(DOUBLE_WRAP_CLASS); JAVA_BASE_TYPE_LIST.add(SHORT_WRAP_CLASS); JAVA_BASE_TYPE_LIST.add(BOOLEAN_WRAP_CLASS); JAVA_BASE_TYPE_LIST.add(BYTE_WRAP_CLASS); JAVA_BASE_TYPE_LIST.add(CHAR_WRAP_CLASS); } /** * 檢查是否是基本資料型別(包括基本資料型別的包裝類) * * @param aClass * @return */ public static boolean isBaseClass(Class aClass) { int indexOf = JAVA_BASE_TYPE_LIST.indexOf(aClass); return indexOf != -1; } 。。。 }
這樣只需要判斷這個Class 在不在JAVA_BASE_TYPE_LIST 中就好了。
接下來我們開始寫資料轉換的,因為基本型別的包裝類基本上都有直接轉換的方法,我們一一呼叫就好了,程式碼是這樣的:
/** * 基本資料型別的轉換 * * @author hjx */ public class BaseTypeValueConverter implements ValueConverter { /** * 非陣列型別,取出陣列中的第一個引數 * * @param value * @param valueClass * @param <T> * @return */ @Override public <T> T converter(String[] value, Class<T> valueClass) { Assert.notNull(value); Assert.isTrue(!valueClass.isArray(), "valueClass 不能是陣列型別!"); String val = value[0]; Assert.notNull(val); if (valueClass.equals(ClassUtils.INT_CLASS) || valueClass.equals(ClassUtils.INT_WRAP_CLASS)) { Object object = Integer.parseInt(val); return (T) object; } if (valueClass.equals(ClassUtils.LONG_CLASS) || valueClass.equals(ClassUtils.LONG_WRAP_CLASS)) { Object object = Long.parseLong(val); return (T) object; } if (valueClass.equals(ClassUtils.FLOAT_CLASS) || valueClass.equals(ClassUtils.FLOAT_WRAP_CLASS)) { Object object = Float.parseFloat(val); return (T) object; } if (valueClass.equals(ClassUtils.DOUBLE_CLASS) || valueClass.equals(ClassUtils.DOUBLE_WRAP_CLASS)) { Object object = Double.parseDouble(val); return (T) object; } if (valueClass.equals(ClassUtils.SHORT_CLASS) || valueClass.equals(ClassUtils.SHORT_WRAP_CLASS)) { Object object = Short.parseShort(val); return (T) object; } if (valueClass.equals(ClassUtils.BYTE_CLASS) || valueClass.equals(ClassUtils.BYTE_WRAP_CLASS)) { Object object = Byte.parseByte(val); return (T) object; } else if (valueClass.equals(ClassUtils.BOOLEAN_CLASS) || valueClass.equals(ClassUtils.BOOLEAN_WRAP_CLASS)) { Object object = Boolean.parseBoolean(val); return (T) object; } if (valueClass.equals(ClassUtils.CHAR_CLASS) || valueClass.equals(ClassUtils.CHAR_WRAP_CLASS)) { Assert.isTrue(val.length() == 1, "引數長度異常,無法轉換char型別!"); Object object = val.charAt(0); return (T) object; } if (valueClass.equals(ClassUtils.STRING_CLASS)) { Object object = val; return (T) object; } throw new UnsupportedOperationException("型別異常,非基本資料型別!"); } }
這裡基本資料型別的轉換就寫好了。接下來就是處理陣列了,因為事先聲明瞭,只做基本資料型別的轉換,所以陣列也只能是基本資料型別的。
基本資料型別陣列的轉換
那麼怎麼判斷一個Class是不是陣列呢?上網搜了搜java的Class 的api 發現其中有兩個方法
//判斷是不是一個數組 public native boolean isArray(); //在Class是一個數組的情況下,返回陣列中元素的Class //Class不是陣列的情況下返回null public native Class<?> getComponentType();
接下來就可以寫程式碼了
import com.hebaibai.amvc.utils.Assert; /** * 基本資料型別的轉換 * * @author hjx */ public class BaseTypeArrayValueConverter extends BaseTypeValueConverter implements ValueConverter { @Override public <T> T converter(String[] value, Class<T> valueClass) { Assert.notNull(value); Assert.notNull(valueClass); Assert.isTrue(valueClass.isArray(), "valueClass 必須是陣列型別!"); Class componentType = valueClass.getComponentType(); Assert.isTrue(!componentType.isArray(), "valueClass 不支援多元陣列!"); Object[] object = new Object[value.length]; for (int i = 0; i < value.length; i++) { object[i] = super.converter(new String[]{value[i]}, componentType); } return (T) object; } }
這樣這兩個轉換器就寫完了。
BUT
現在只有轉換器,工廠類中根據什麼樣的邏輯獲取什麼樣的轉換器還沒寫,現在給補上
import com.hebaibai.amvc.utils.ClassUtils; /** * 資料轉換器工廠類 */ public class ValueConverterFactory { /** * 基本資料型別的資料轉換 */ private static final ValueConverter BASE_TYPE_VALUE_CONVERTER = new BaseTypeValueConverter(); /** * 基本型別陣列的資料轉換 */ private static final ValueConverter BASE_TYPE_ARRAY_VALUE_CONVERTER = new BaseTypeArrayValueConverter(); /** * 根據目標型別獲取轉換器 * * @param valueClass * @return */ public static ValueConverter getValueConverter(Class valueClass) { boolean baseClass = ClassUtils.isBaseClass(valueClass); if (baseClass) { return BASE_TYPE_VALUE_CONVERTER; } if (valueClass.isArray()) { return BASE_TYPE_ARRAY_VALUE_CONVERTER; } throw new UnsupportedOperationException("資料型別:" + valueClass.getName() + " 不支援轉換!"); } }
這樣就萬事大吉了~~~
再說點啥
之後想要新增其他的型別轉換的話,只需要新寫幾個實現類,然後修改一下工廠程式碼就好了,比較好擴充套件。這也是寫工廠類的原因。
寫段程式碼測試一下吧
MethodValueGetter methodValueGetter = new MethodValueGetter(); //拼裝測試資料 Map<String, String[]> value = new HashMap<>(); value.put("name", new String[]{"何白白"}); value.put("age", new String[]{"20"}); value.put("children", new String[]{"何大白1", "何大白2", "何大白3", "何大白4"}); //執行方法 Object[] methodValue = methodValueGetter.getMethodValue( new Class[]{String.class, int.class, String[].class},//入參中的引數型別 new String[]{"name", "age", "children"},//入參的引數名稱 value//請求中的引數 ); //列印結果 for (int i = 0; i < methodValue.length; i++) { Object obj = methodValue[i]; if (obj == null) { System.out.println("null"); continue; } Class<?> objClass = obj.getClass(); if (objClass.isArray()) { Object[] objects = (Object[]) obj; for (Object object : objects) { System.out.println(object + "===" + object.getClass()); } } else { System.out.println(obj + "===" + obj.getClass()); } }
結果:
何白白===class java.lang.String 20===class java.lang.Integer 何大白1===class java.lang.String 何大白2===class java.lang.String 何大白3===class java.lang.String 何大白4===class java.lang.String
測試成功~~~
最後
現在通過反射執行Method的引數我們也已經拿到了,接下來就是執行了,下一篇在寫吧
拜拜
對了,程式碼同步更新在我的github上 https://github.com/hjx601496320/aMvc 歡迎來看~