zoukankan      html  css  js  c++  java
  • 自己写一个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 欢迎来看~

  • 相关阅读:
    docker 安装镜像
    Vagrant+Oracle VM VirtualBox创建linux虚拟机(centos7)
    idea配置git,github , gitee
    idea连接数据库
    idea基本设置
    git基础命令
    mybatis中的where
    重学 Java 设计模式:实战桥接模式(多支付渠道「微信、支付宝」与多支付模式「刷脸、指纹」场景)
    HTTPS加密原理
    优惠券数据库设计
  • 原文地址:https://www.cnblogs.com/hebaibai/p/10346162.html
Copyright © 2011-2022 走看看