上一篇
自己寫一個mvc框架吧(三)
專案地址在:github.com/hjx60149632… 。
測試程式碼在:github.com/hjx60149632… 。
根據Method獲取引數並轉換引數型別
上一篇我們將url與Method的對映建立完畢,併成功的將對映關係建立起來了。這一篇我們將根據Method的入參引數名稱、引數型別來獲取引數,並轉換引數型別,使其能夠符合Method的定義。
事先說明
因為這裡只是一個mvc框架的簡單實現,僅僅只做到了基本資料型別和基本資料型別包裝類的轉換,沒有做到spring那樣的很複雜的資料繫結功能。所以我在程式碼上面加了比較強的校驗。
現在開始寫吧
我們從一次http請求中獲取引數的時候,一般需要知道引數的名稱,引數名稱我們可以用方法的入參名稱。這一步我們已經做好了(可以看上一篇:www.cnblogs.com/hebaibai/p/… )。
在這裡我們需要定義一個方法,用來從請求中的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;
}
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上 github.com/hjx60149632… 歡迎來看~