自己寫一個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;
        } 
        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上 https://github.com/hjx601496320/aMvc 歡迎來看~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM