自己写一个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 欢迎来看~

自己写一个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. 设置 Nuget 本地源、在线私有源、自动构建打包

    设置 Nuget 本地源.在线私有源.自动构建打包 本文演示如果在项目中生成 Nuget 包,并添加 Nuget 本地源,不用发布到 Nuget 服务器.再附带使用在线私有源的简单方法,以及提交代码自 ...

  2. 在我朝如何为Brackets安装扩展?

    Brackets是Adobe发起的一个开源的HTML编辑器,界面优美简洁.扩展丰富.支持基于Chrome浏览器的实时预览,非常适合对于Web的开发和学习. 但是Brackets的安装包下载和扩展下载, ...

  3. 使用WPF制作视频监控多画面切换

    前言 曾有做过一个产品,有一个功能是视频监控模块,视频监控首先想到的是视频多画面切换功能,由于前端是用WPF开发的,所以当时就做了一个多画面切换组件,效果如下: 功能设计前提: 由于要使用海康大华天地 ...

  4. 【转】C#如何创建泛型类T的实例

    原文地址:https://www.cnblogs.com/lxhbky/p/6020612.html 最近在学历基类的写法时,遇到了一个问题:如何怎么创建一个泛型类T的实例呢?     废话不多说了, ...

  5. SVM的代码实现-python

    隔了好久木有更新了,因为发现自己numpy的很多操作都忘记了,加上最近有点忙... 接着上次 我们得到的迭代函数为 首先j != yi j = yi import numpy as np def sv ...

  6. Linux学习笔记-基本操作4

    1. gdb调试2. makefile的编写3. 系统IO函数 1. gdb调试:        1. 启动gdb + 可执行文件        2. 查看代码:                l== ...

  7. flask_mysql入库

    mysql 的入库和MongoDB的有一点点的区别 不过都很重要,都必须要掌握的技能, 现在我来演示一下mysql入库的过程: 首先  我们要导包,这是必不可少的一部分,都不用我说了吧 #导报 imp ...

  8. 配置django图片上传与保存展示

    近来在研究django,发现有好多好玩的功能,比如图片上传,以前处理这个比较麻烦,现在我们来看看如何来处理图片上传与保存 1.在数据库设计的时候需要配置upload_to image = models ...

  9. POJ 2421

    #include<iostream> #include<stdio.h> #define MAXN 500 #define inf 100000000 using namesp ...

  10. 课程一(Neural Networks and Deep Learning),第二周(Basics of Neural Network programming)—— 3、Python Basics with numpy (optional)

    Python Basics with numpy (optional)Welcome to your first (Optional) programming exercise of the deep ...