自己写一个mvc框架吧(三)

根据Method获取参数并转换参数类型

上一篇我们将url与Method的映射创建完毕,并成功的将映射关系创建起来了。这一篇我们将根据Method的入参参数名称、参数类型来获取参数,并转换参数类型,使其能够符合Method的定义

事先说明

因为这里只是一个mvc框架的简单实现,仅仅只做到了基本数据类型基本数据类型包装类的转换,没有做到spring那样的很复杂的数据绑定功能。所以我在代码上面加了比较强的校验。

现在开始写吧

我们从一次http请求中获取参数的时候,一般需要知道参数的名称,参数名称我们可以用方法的入参名称。这一步我们已经做好了(可以看上一篇:https://www.cnblogs.com/hebaibai/p/10340884.html )。

在这里我们需要定义一个方法,用来从请求中的String类型的参数转换成为我们定义的Method的入参类型。至于为啥请求中获取的参数是String类型的可以查看一下ServletRequest.java中的方法定义。这里就不讲啦~。所以我们这个方法可以是这样的:

  1. /**
  2. * 获取方法的入参
  3. *
  4. * @param valueTypes
  5. * @param valueNames
  6. * @param valueSource
  7. * @return
  8. */
  9. public Object[] getMethodValue(Class[] valueTypes, String[] valueNames, Map<String, String[]> valueSource){
  10. 。。。
  11. }

这里接受三个参数

1:valueTypes 这个是Method的入参类型

2:valueNames 这个是Method的入参参数名称

3:valueSource 这个是一次请求中所有的参数。这个参数是一个Map。value的泛型是一个String数组,这里用数组的原因是因为在一次请求中,名称相同的参数可能会有多个。可以查看ServletRequest.java中的方法:

  1. public String[] getParameterValues(String name);

说明一下

这里我依然不写Servlet,因为还不到时候,我们可以在整个代码的架子都写起来之后,每一部分都经过单元测试之后,最后再写这个入口。就像搭积木一样,先把每一块都准备好,最后将所有的拼起来就好了。

继续

现在这个获取方法请求入参的方法定义完了,接下来怎么样根据参数类型将String类型的参数转换出来是一个麻烦的事情,这里要写好多if else的代码。我们一步一步的写,先写一个基本数据类型转换的。

数据类型转换

这里定义一个接口 ValueConverter.java,里面只有一个方法,用于做数据转换

  1. <T> T converter(String[] value, Class<T> valueClass);

有同学要问了,这里为啥要定义成一个接口呢?为啥不直接写一个Class,里面直接写实现代码呢?

因为我这里还有一个工厂类要用来获取ValueConverter.java的实现呀!工厂类的代码张这个样子

  1. /**
  2. * 数据转换器工厂类
  3. */
  4. public class ValueConverterFactory {
  5. /**
  6. * 根据目标类型获取转换器
  7. *
  8. * @param valueClass
  9. * @return
  10. */
  11. public static ValueConverter getValueConverter(Class valueClass) {
  12. if(...){
  13. return ValueConverter;
  14. }
  15. throw new UnsupportedOperationException("数据类型:" + valueClass.getName() + " 不支持转换!");
  16. }
  17. }

为啥要写这个工厂类呢?还要从接口 ValueConverter.java说起,java中的接口(interface并不是为了在开发中写一个service或者写一个DAO让代码好看而定义的,而是让我们定义标准的。规定在这个标准中每个方法的入参、出参、异常信息、方法名称以及这个方法是用来做什么的。只要是这个接口的实现类,就必须要遵守这个标准。调用者在调用的时候也不需要知道它调用的是哪一个实现类,只要按照接口标准进行传参,就可以拿到想要的出参。

所以我们在使用这一段代码的时候只需要给ValueConverterFactory传如一个Class,工厂类返回一个可以转换这个Class的实现就好了。

将上面的 getMethodValue 补充完毕就是这个样子:

  1. /**
  2. * 获取方法的入参
  3. *
  4. * @param valueTypes
  5. * @param valueNames
  6. * @param valueSource
  7. * @return
  8. */
  9. public Object[] getMethodValue(Class[] valueTypes, String[] valueNames, Map<String, String[]> valueSource) {
  10. Assert.notNull(valueTypes);
  11. Assert.notNull(valueNames);
  12. Assert.notNull(valueSource);
  13. Assert.isTrue(valueNames.length == valueTypes.length,
  14. "getMethodValue() 参数长度不一致!");
  15. int length = valueNames.length;
  16. Object[] values = new Object[length];
  17. for (int i = 0; i < values.length; i++) {
  18. Class valueType = valueTypes[i];
  19. String valueName = valueNames[i];
  20. String[] strValues = valueSource.get(valueName);
  21. //来源参数中 key不存在或者key的值不存在,设置值为null
  22. if (strValues == null) {
  23. values[i] = null;
  24. continue;
  25. }
  26. ValueConverter valueConverter = ValueConverterFactory.getValueConverter(valueType);
  27. Object converter = valueConverter.converter(strValues, valueType);
  28. values[i] = converter;
  29. }
  30. return values;
  31. }

在这里就可以看到,我们在调用工厂类的getValueConverter方法,工厂类就会给我们一个转换器 ValueConverter ,我们只需要用它来进行转换就好了,不需要知道是怎么转换的。

但是我们还是要先写几个转换器,因为现在并没有真正可用的转换器,有的只是标准。现在我们先写一个基本数据类型的转换器。

基本数据类型的转换

在这里,我们先要通过Class判断一下它是不是一个基本类型,注意:

这里我说的基本数据类型是指 java中的 基本数据类型 和 它们的包装类 以及 String

先写一个工具类:

  1. public class ClassUtils {
  2. /**
  3. * java 基本类型
  4. */
  5. public static List<Class> JAVA_BASE_TYPE_LIST = new ArrayList<>();
  6. public final static Class INT_CLASS = int.class;
  7. public final static Class LONG_CLASS = long.class;
  8. public final static Class FLOAT_CLASS = float.class;
  9. public final static Class DOUBLE_CLASS = double.class;
  10. public final static Class SHORT_CLASS = short.class;
  11. public final static Class BYTE_CLASS = byte.class;
  12. public final static Class BOOLEAN_CLASS = boolean.class;
  13. public final static Class CHAR_CLASS = char.class;
  14. public final static Class STRING_CLASS = String.class;
  15. public final static Class INT_WRAP_CLASS = Integer.class;
  16. public final static Class LONG_WRAP_CLASS = Long.class;
  17. public final static Class FLOAT_WRAP_CLASS = Float.class;
  18. public final static Class DOUBLE_WRAP_CLASS = Double.class;
  19. public final static Class SHORT_WRAP_CLASS = Short.class;
  20. public final static Class BOOLEAN_WRAP_CLASS = Boolean.class;
  21. public final static Class BYTE_WRAP_CLASS = Byte.class;
  22. public final static Class CHAR_WRAP_CLASS = Character.class;
  23. static {
  24. //基本数据类型
  25. JAVA_BASE_TYPE_LIST.add(INT_CLASS);
  26. JAVA_BASE_TYPE_LIST.add(LONG_CLASS);
  27. JAVA_BASE_TYPE_LIST.add(FLOAT_CLASS);
  28. JAVA_BASE_TYPE_LIST.add(DOUBLE_CLASS);
  29. JAVA_BASE_TYPE_LIST.add(SHORT_CLASS);
  30. JAVA_BASE_TYPE_LIST.add(BYTE_CLASS);
  31. JAVA_BASE_TYPE_LIST.add(BOOLEAN_CLASS);
  32. JAVA_BASE_TYPE_LIST.add(CHAR_CLASS);
  33. //基本数据类型(对象)
  34. JAVA_BASE_TYPE_LIST.add(STRING_CLASS);
  35. JAVA_BASE_TYPE_LIST.add(INT_WRAP_CLASS);
  36. JAVA_BASE_TYPE_LIST.add(LONG_WRAP_CLASS);
  37. JAVA_BASE_TYPE_LIST.add(FLOAT_WRAP_CLASS);
  38. JAVA_BASE_TYPE_LIST.add(DOUBLE_WRAP_CLASS);
  39. JAVA_BASE_TYPE_LIST.add(SHORT_WRAP_CLASS);
  40. JAVA_BASE_TYPE_LIST.add(BOOLEAN_WRAP_CLASS);
  41. JAVA_BASE_TYPE_LIST.add(BYTE_WRAP_CLASS);
  42. JAVA_BASE_TYPE_LIST.add(CHAR_WRAP_CLASS);
  43. }
  44. /**
  45. * 检查是否是基本数据类型(包括基本数据类型的包装类)
  46. *
  47. * @param aClass
  48. * @return
  49. */
  50. public static boolean isBaseClass(Class aClass) {
  51. int indexOf = JAVA_BASE_TYPE_LIST.indexOf(aClass);
  52. return indexOf != -1;
  53. }
  54. 。。。
  55. }

这样只需要判断这个Class 在不在 JAVA_BASE_TYPE_LIST 中就好了。

接下来我们开始写数据转换的,因为基本类型的包装类基本上都有直接转换的方法,我们一一调用就好了,代码是这样的:


  1. /**
  2. * 基本数据类型的转换
  3. *
  4. * @author hjx
  5. */
  6. public class BaseTypeValueConverter implements ValueConverter {
  7. /**
  8. * 非数组类型,取出数组中的第一个参数
  9. *
  10. * @param value
  11. * @param valueClass
  12. * @param <T>
  13. * @return
  14. */
  15. @Override
  16. public <T> T converter(String[] value, Class<T> valueClass) {
  17. Assert.notNull(value);
  18. Assert.isTrue(!valueClass.isArray(), "valueClass 不能是数组类型!");
  19. String val = value[0];
  20. Assert.notNull(val);
  21. if (valueClass.equals(ClassUtils.INT_CLASS) || valueClass.equals(ClassUtils.INT_WRAP_CLASS)) {
  22. Object object = Integer.parseInt(val);
  23. return (T) object;
  24. }
  25. if (valueClass.equals(ClassUtils.LONG_CLASS) || valueClass.equals(ClassUtils.LONG_WRAP_CLASS)) {
  26. Object object = Long.parseLong(val);
  27. return (T) object;
  28. }
  29. if (valueClass.equals(ClassUtils.FLOAT_CLASS) || valueClass.equals(ClassUtils.FLOAT_WRAP_CLASS)) {
  30. Object object = Float.parseFloat(val);
  31. return (T) object;
  32. }
  33. if (valueClass.equals(ClassUtils.DOUBLE_CLASS) || valueClass.equals(ClassUtils.DOUBLE_WRAP_CLASS)) {
  34. Object object = Double.parseDouble(val);
  35. return (T) object;
  36. }
  37. if (valueClass.equals(ClassUtils.SHORT_CLASS) || valueClass.equals(ClassUtils.SHORT_WRAP_CLASS)) {
  38. Object object = Short.parseShort(val);
  39. return (T) object;
  40. }
  41. if (valueClass.equals(ClassUtils.BYTE_CLASS) || valueClass.equals(ClassUtils.BYTE_WRAP_CLASS)) {
  42. Object object = Byte.parseByte(val);
  43. return (T) object;
  44. }
  45. if (valueClass.equals(ClassUtils.BOOLEAN_CLASS) || valueClass.equals(ClassUtils.BOOLEAN_WRAP_CLASS)) {
  46. Object object = Boolean.parseBoolean(val);
  47. return (T) object;
  48. }
  49. if (valueClass.equals(ClassUtils.CHAR_CLASS) || valueClass.equals(ClassUtils.CHAR_WRAP_CLASS)) {
  50. Assert.isTrue(val.length() == 1, "参数长度异常,无法转换char类型!");
  51. Object object = val.charAt(0);
  52. return (T) object;
  53. }
  54. if (valueClass.equals(ClassUtils.STRING_CLASS)) {
  55. Object object = val;
  56. return (T) object;
  57. }
  58. throw new UnsupportedOperationException("类型异常,非基本数据类型!");
  59. }
  60. }

这里基本数据类型的转换就写好了。接下来就是处理数组了,因为事先声明了,只做基本数据类型的转换,所以数组也只能是基本数据类型的。

基本数据类型数组的转换

那么怎么判断一个Class是不是数组呢?上网搜了搜java的Classapi发现其中有两个方法

  1. //判断是不是一个数组
  2. public native boolean isArray();
  3. //在Class是一个数组的情况下,返回数组中元素的Class
  4. //Class不是数组的情况下返回null
  5. public native Class<?> getComponentType();

接下来就可以写代码了


  1. import com.hebaibai.amvc.utils.Assert;
  2. /**
  3. * 基本数据类型的转换
  4. *
  5. * @author hjx
  6. */
  7. public class BaseTypeArrayValueConverter extends BaseTypeValueConverter implements ValueConverter {
  8. @Override
  9. public <T> T converter(String[] value, Class<T> valueClass) {
  10. Assert.notNull(value);
  11. Assert.notNull(valueClass);
  12. Assert.isTrue(valueClass.isArray(), "valueClass 必须是数组类型!");
  13. Class componentType = valueClass.getComponentType();
  14. Assert.isTrue(!componentType.isArray(), "valueClass 不支持多元数组!");
  15. Object[] object = new Object[value.length];
  16. for (int i = 0; i < value.length; i++) {
  17. object[i] = super.converter(new String[]{value[i]}, componentType);
  18. }
  19. return (T) object;
  20. }
  21. }

这样这两个转换器就写完了。

BUT

现在只有转换器,工厂类中根据什么样的逻辑获取什么样的转换器还没写,现在给补上


  1. import com.hebaibai.amvc.utils.ClassUtils;
  2. /**
  3. * 数据转换器工厂类
  4. */
  5. public class ValueConverterFactory {
  6. /**
  7. * 基本数据类型的数据转换
  8. */
  9. private static final ValueConverter BASE_TYPE_VALUE_CONVERTER = new BaseTypeValueConverter();
  10. /**
  11. * 基本类型数组的数据转换
  12. */
  13. private static final ValueConverter BASE_TYPE_ARRAY_VALUE_CONVERTER = new BaseTypeArrayValueConverter();
  14. /**
  15. * 根据目标类型获取转换器
  16. *
  17. * @param valueClass
  18. * @return
  19. */
  20. public static ValueConverter getValueConverter(Class valueClass) {
  21. boolean baseClass = ClassUtils.isBaseClass(valueClass);
  22. if (baseClass) {
  23. return BASE_TYPE_VALUE_CONVERTER;
  24. }
  25. if (valueClass.isArray()) {
  26. return BASE_TYPE_ARRAY_VALUE_CONVERTER;
  27. }
  28. throw new UnsupportedOperationException("数据类型:" + valueClass.getName() + " 不支持转换!");
  29. }
  30. }

这样就万事大吉了~~~

再说点啥

之后想要添加其他的类型转换的话,只需要新写几个实现类,然后修改一下工厂代码就好了,比较好扩展。这也是写工厂类的原因。

写段代码测试一下吧

  1. MethodValueGetter methodValueGetter = new MethodValueGetter();
  2. //拼装测试数据
  3. Map<String, String[]> value = new HashMap<>();
  4. value.put("name", new String[]{"何白白"});
  5. value.put("age", new String[]{"20"});
  6. value.put("children", new String[]{"何大白1", "何大白2", "何大白3", "何大白4"});
  7. //执行方法
  8. Object[] methodValue = methodValueGetter.getMethodValue(
  9. new Class[]{String.class, int.class, String[].class},//入参中的参数类型
  10. new String[]{"name", "age", "children"},//入参的参数名称
  11. value//请求中的参数
  12. );
  13. //打印结果
  14. for (int i = 0; i < methodValue.length; i++) {
  15. Object obj = methodValue[i];
  16. if (obj == null) {
  17. System.out.println("null");
  18. continue;
  19. }
  20. Class<?> objClass = obj.getClass();
  21. if (objClass.isArray()) {
  22. Object[] objects = (Object[]) obj;
  23. for (Object object : objects) {
  24. System.out.println(object + "===" + object.getClass());
  25. }
  26. } else {
  27. System.out.println(obj + "===" + obj.getClass());
  28. }
  29. }

结果:

  1. 何白白===class java.lang.String
  2. 20===class java.lang.Integer
  3. 何大白1===class java.lang.String
  4. 何大白2===class java.lang.String
  5. 何大白3===class java.lang.String
  6. 何大白4===class java.lang.String

测试成功~~~

最后

现在通过反射执行Method的参数我们也已经拿到了,接下来就是执行了,下一篇在写吧

拜拜

对了,代码同步更新在我的github上 https://github.com/hjx601496320/aMvc 欢迎来看~

自己写一个java的mvc框架吧(三)的更多相关文章

  1. 自己写一个java的mvc框架吧(五)

    自己写一个mvc框架吧(五) 给框架添加注解的支持 一段废话 上一章本来是说这一章要写视图处理的部分,但是由于我在测试代码的时候需要频繁的修改配置文件,太麻烦了.所以这一章先把支持注解的功能加上,这样 ...

  2. 自己写一个java的mvc框架吧(四)

    自己写一个mvc框架吧(四) 写一个请求的入口,以及初始化框架 上一章写了获取方法的入参,并根据入参的参数类型进行数据转换.这时候,我们已经具备了通过反射调用方法的一切必要条件.现在我们缺少一个htt ...

  3. 自己写一个java的mvc框架吧(二)

    自己写一个mvc框架吧(二) 自己写代码的习惯 写一个框架吧,如果这个框架会用到一些配置上的东西,我自己习惯是先不用考虑这个配置文件应该是怎样的,什么形式的,先用一个java对象(比如叫 Config ...

  4. 自己写一个java的mvc框架吧(一)

    自己写一个mvc框架吧(一) 目录 自己写一个mvc框架吧(一) 自己写一个mvc框架吧(二) 自己写一个mvc框架吧(三) 自己写一个mvc框架吧(四) 写之前的一些废话 废话 1 (总是要先随便说 ...

  5. 自己动手写一个简单的MVC框架(第一版)

    一.MVC概念回顾 路由(Route).控制器(Controller).行为(Action).模型(Model).视图(View) 用一句简单地话来描述以上关键点: 路由(Route)就相当于一个公司 ...

  6. 自己动手写一个简单的MVC框架(第二版)

    一.ASP.NET MVC核心机制回顾 在ASP.NET MVC中,最核心的当属“路由系统”,而路由系统的核心则源于一个强大的System.Web.Routing.dll组件. 在这个System.W ...

  7. AsMVC:一个简单的MVC框架的Java实现

    当初看了<从零开始写一个Java Web框架>,也跟着写了一遍,但当时学艺不精,真正进脑子里的并不是很多,作者将依赖注入框架和MVC框架写在一起也给我造成了不小的困扰.最近刚好看了一遍sp ...

  8. Summer——从头开始写一个简易的Spring框架

    Summer--从头开始写一个简易的Spring框架                ​ 参考Spring框架实现一个简易类似的Java框架.计划陆续实现IOC.AOP.以及数据访问模块和事务控制模块. ...

  9. 用Python写一个简单的Web框架

    一.概述 二.从demo_app开始 三.WSGI中的application 四.区分URL 五.重构 1.正则匹配URL 2.DRY 3.抽象出框架 六.参考 一.概述 在Python中,WSGI( ...

随机推荐

  1. CentOS7下让Asp.Net Core的网站自动运行

    一.安装Nginx yum install nginx 二.配置Nginx vi /etc/nginx/nginx.conf location / { proxy_pass http://127.0. ...

  2. [leetcode.com]算法题目 - Plus One

    Given a number represented as an array of digits, plus one to the number. class Solution { public: v ...

  3. SpringBoot2 web

    验证框架 SpringBoot支持JSR-303,Bean等验证框架 JSR-303 JSR-303是Java的标准验证框架,已有实现Hibernate validator. JSR-303验证类型 ...

  4. Python 关于 encode与decode 中文乱码问题

    字符串在Python内部的表示是unicode编码,因此,在做编码转换时,通常需要以unicode作为中间编码,即先将其他编码的字符串解码(decode)成unicode,再从unicode编码(en ...

  5. 前端基础-html 列表标签,表格标签,表单标签

    一.列表标签 1.ul(无序列表)标签 ul(unordered list)无序列表,ul下的子元素只能是li(list item),如下示例: <ul> <li>第一项< ...

  6. 服务器返回的http状态码

    状态码 响应类别 原因短语 1XX 信息性状态码(Informational) 服务器正在处理请求 2XX 成功状态码(Success) 请求已正常处理完毕 3XX 重定向状态码(Redirectio ...

  7. C# 多线程学习系列三之CLR线程池系列之ThreadPool

    一.CLR线程池 1.进程和CLR的关系一个进程可以只包含一个CLR,也可以包含多个CLR2.CLR和AppDomain的关系一个CLR可以包含多个AppDomain3.CLR和线程池的关系一个CLR ...

  8. struts和hibernate整合

    程序示例: 1.引入jar包 2.实体对象 Dept.java package com.gqx.entity; import java.util.HashSet; import java.util.S ...

  9. 18-hadoop-weather案例

    weather案例, 简单分析每年的前三个月的最高温即可, 使用自定义的分组和排序 设计分析 设定多个reduce 每年的数据都很多,如果按照默认情况处理,统计性能是非常慢(因为默认只有一个reduc ...

  10. idea @Override is not allowed when implementing interface method

    转自:http://blog.csdn.net/shenya2/article/details/50460447 在编码过程发现报错:@Override is not allowed when imp ...