从底层看springmvc数据绑定

从底层看springmvc数据绑定:

springmvc 的数据绑定简单来说就是将http 报文进行解析,并将数据部分的数据和头字段的数据存入到指定的内存空间当中,而外在的体现就是接收页面传递的表单数据,并将数据进行一定方式的转换,绑定到控制器类(controller)的方法参数中,从而可以省去从信息流中获取数据到重新构建对象的操作部分,通过面向对象的方式直接操作数据,可以说是对底层HttpServlet 的封装,可以简化编码的过程和提高编码效率。

如果想要更深的理解和简易实现这个过程需要做以下几步工作:

  • 接收客户端(浏览器)传递的数据
  • 解析接收到的数据
  • 将数据注入到controller中某一方法的参数中

1)接收数据

编写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 发送测试数据:

从底层看springmvc数据绑定_第1张图片

这里我们通过的是表单数据传输,其实本质上就是将数据并到url中进行传输,而后台servlet获取这类数据的常见的方法是request.getParameter 方法,这和我们常说http数据部分其实是没有关系的,因为这部分数据是在http 报文头字段进行传输的:

从底层看springmvc数据绑定_第2张图片

这里url 部分传递数据中有乱码,其实是通过BASE64 进行编码的中文字符。

如果要将数据写入到报文的数据部分就需要:

从底层看springmvc数据绑定_第3张图片

测试:

从底层看springmvc数据绑定_第4张图片

当然url和data 部分是可以同时进行数据传输的,从这里角度可以再进行一次延展和延申:

因为Socket 是运输层传递数据的端点,所以完全可以根据Socket 本身去定制属于自己的数据传输的应用层协议。

2)解析数据

得到了协议的内容,下一步就是针对协议内容进行数据解析,这一部分对应Servlet 就是封装出HttpRequest 对象,这部分看着复杂,其实就是根据固定格式进行封装转换,这里提供一种简单的解析方式:将报文转换为字符串,将各个头字段的Key 定制为常量,然后将数据放到一个Map中:

参考链接:Java实现简单静态-动态http服务器_Juffery_Wang的博客-CSDN博客

当然Servlet 底层对于HttpRequest 的转换是面向字节并且效率更高的解析方式,这里就不过多赘述了,因为解析本身并不是本节的主要内容。

3)注入数据

数据被解析成对象,就会加载到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));
   }
}

image-20220106204550323

4)小结

通过上面的方式,我们就实现了如何接收客户端传递的http 数据报,如何将http 数据报解析为可操作对象,如何将可操作对象存储的数据封装到指定类型对象当中,如果想像成熟的框架一样把这些内容做的简略而且优雅,比如说将数据注入的部分通过注解的方式实现,不就能优雅的注入数据信息了吗?

如何通过动态代理的方式实现注解,也可以参考我的博客:

动态代理及JDK动态代理源码解析 - 云里云外开源社区 (yunliyunwai.cn)

实现对@RequestParam校验 - 云里云外开源社区 (yunliyunwai.cn)

你可能感兴趣的:(JAVA,java,服务器,springmvc)