springmvc 的数据绑定简单来说就是将http 报文进行解析,并将数据部分的数据和头字段的数据存入到指定的内存空间当中,而外在的体现就是接收页面传递的表单数据,并将数据进行一定方式的转换,绑定到控制器类(controller)的方法参数中,从而可以省去从信息流中获取数据到重新构建对象的操作部分,通过面向对象的方式直接操作数据,可以说是对底层HttpServlet 的封装,可以简化编码的过程和提高编码效率。
如果想要更深的理解和简易实现这个过程需要做以下几步工作:
编写ServerSocketApplication
public class TestServlet {
public static void main(String[] args) throws IOException {
/*编写服务器Socket 并绑定8080端口*/
ServerSocket serverSocket = new ServerSocket(8080);
while (true) {
/*同步阻塞 等待链接建立*/
Socket incoming = serverSocket.accept();
System.out.println("连接建立:" + incoming);
InputStream in = incoming.getInputStream();
/*封装接收流到写入器*/
Scanner scanner = new Scanner(in, "UTF-8");
System.out.println("HttpMessage:");
boolean flag = false;
while (!flag && scanner.hasNextLine()) {
/*读取用户的输入*/
String line = scanner.nextLine();
/*输出用户输入的数据*/
System.out.println(line);
}
}
}
}
通过postman 发送测试数据:
这里我们通过的是表单数据传输,其实本质上就是将数据并到url中进行传输,而后台servlet获取这类数据的常见的方法是request.getParameter 方法,这和我们常说http数据部分其实是没有关系的,因为这部分数据是在http 报文头字段进行传输的:
这里url 部分传递数据中有乱码,其实是通过BASE64 进行编码的中文字符。
如果要将数据写入到报文的数据部分就需要:
测试:
当然url和data 部分是可以同时进行数据传输的,从这里角度可以再进行一次延展和延申:
因为Socket 是运输层传递数据的端点,所以完全可以根据Socket 本身去定制属于自己的数据传输的应用层协议。
得到了协议的内容,下一步就是针对协议内容进行数据解析,这一部分对应Servlet 就是封装出HttpRequest 对象,这部分看着复杂,其实就是根据固定格式进行封装转换,这里提供一种简单的解析方式:将报文转换为字符串,将各个头字段的Key 定制为常量,然后将数据放到一个Map中:
参考链接:Java实现简单静态-动态http服务器_Juffery_Wang的博客-CSDN博客
当然Servlet 底层对于HttpRequest 的转换是面向字节并且效率更高的解析方式,这里就不过多赘述了,因为解析本身并不是本节的主要内容。
数据被解析成对象,就会加载到JVM的堆内存中,但是还无法以对象的形式进行操作和调用,下一步就是如何将数据传递到方法的参数中,也是数据绑定的最后一步,其实这里也很简单,这里也是给到一个简单的实现,核心的思路就是通过反射向对象中注入数据:
package util; import pojo.User; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.Map; /** * @author: Jeffrey * @date: 2022/01/06/19:57 * @description: */ public class Valuation<T> { /** * * @param map 存储前端传递的数据,key:属性名 * @param tClass * @return */ private T set(Map<String, Object> map, Class<T> tClass) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { Constructor constructor = tClass.getConstructor(); T t = (T) constructor.newInstance(); Field[] fields = tClass.getDeclaredFields(); for (int i = 0 ; i < fields.length ;i++){ if (!fields[i].isAccessible()){ fields[i].setAccessible(true); } fields[i].set(t,map.get(fields[i].getName())); } return t; } public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { Valuation valuation = new Valuation(); Map<String,Object> map = new HashMap<>(); map.put("name","Jeffrey"); map.put("age",20); map.put("username","王海澎"); System.out.println(valuation.set(map,User.class)); } }
通过上面的方式,我们就实现了如何接收客户端传递的http 数据报,如何将http 数据报解析为可操作对象,如何将可操作对象存储的数据封装到指定类型对象当中,如果想像成熟的框架一样把这些内容做的简略而且优雅,比如说将数据注入的部分通过注解的方式实现,不就能优雅的注入数据信息了吗?
如何通过动态代理的方式实现注解,也可以参考我的博客:
动态代理及JDK动态代理源码解析 - 云里云外开源社区 (yunliyunwai.cn)
实现对@RequestParam校验 - 云里云外开源社区 (yunliyunwai.cn)