自己寫一個mvc框架吧(三)

何白白發表於2019-02-01
上一篇

自己寫一個mvc框架吧(二)

自己寫一個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的Classapi發現其中有兩個方法

//判斷是不是一個陣列
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… 歡迎來看~

下一篇

自己寫一個mvc框架吧(四)

相關文章