自己写一个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; } else 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 欢迎来看~
完
推荐阅读
-
移动端自适应flexible.js的使用方法(不用三大框架,仅写一个单html页面使用)推荐
-
手把手教你写一个java的orm(三)
-
自己写一个java的mvc框架吧(一)
-
从零写一个具有IOC-AOP-MVC功能的框架---学习笔记---08.框架的AOP功能和IOC功能测试
-
自己写一个java的mvc框架吧(三)
-
荐 从零写一个具有IOC-AOP-MVC功能的框架---学习笔记---10. MVC 结果渲染器的编写
-
自己写一个java的mvc框架吧(二)
-
如何用120行Java代码写一个自己的区块链
-
荐 从零写一个具有IOC-AOP-MVC功能的框架---学习笔记---12.Hello Framewok框架初步使用介绍(测试)+未来计划展望
-
Python文件管理 | 给自己写一个备份文件的程序吧2.0