自己寫一個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;
}
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 歡迎來看~
