手写SpringMVC之调度器DispatcherServlet

news2024/9/20 10:51:18

DispatcherServlet:分发、调度

根据上一节,已经实现了将controller的方法添加到容器中,而DispatcherServlet的作用就是接收来自客户端的请求,然后通过URI的组合,来找到对应的@RequestMapping注解的方法,调用对应的方法,最后返回响应

image-20240627203051007

第一步:获取URI,根据URI来匹配对应的BeanDefinition

String requestURI = req.getRequestURI();

Map<String, BeanDefinition<?>> maps = BeanContainer.getMaps();


//通过匹配URI来找到对应的BeanDefinition
BeanDefinition<?> beanDefinition = maps.get(requestURI);
if (beanDefinition == null) {
    throw new FrameWorkException(ResponseCode.REQUEST_MAPPING_PATH_EXCEPTION.getCode(), ResponseCode.REQUEST_MAPPING_PATH_EXCEPTION.getMessage());
}

第二步:获取容器中的BeanDefinition的MethodDefinition和ParameterDefinition

//获取对应的controller类对象
Object t = beanDefinition.getT();
MethodDefinition methodDefinition = beanDefinition.getMethodDefinition();
//获取方法对象
Method method = methodDefinition.getMethod();
method.setAccessible(true);
//获取参数列表
List<ParameterDefinition> parameterDefinitions = methodDefinition.getParameters();

第三步:调用对应的方法

Object[] args;
Model model = new Model();

try {
    args = handlerParameterArgs(parameterDefinitions, req, resp, model);
    //调用Controller层里某个方法
    Object returnVal = method.invoke(t, args);

    if (returnVal != null) {
        //处理返回值
        handlerReturnVal(methodDefinition, returnVal, req, resp, model);
    }
} catch (Exception e) {
    System.out.println(Arrays.toString(e.getStackTrace()));
    ;
}
handlerParameterArgs:将调用的方法的参数列表与请求数据适配
/**
 * 集中处理参数的函数,通过判断参数的类型,对不同类型的参数进行处理,包括
 * 1. 常见参数类型:八大基本数据类型及其包装类 + String
 * 2. 数组类型
 * 3. HttpServletRequest 类型
 * 4. httpServletResponse 类型
 * 5. List<?> 类型
 * 6. 自定义类型
 *
 * @param parameterDefinitions 参数描述对象列表(从controller的方法下抽取出来的)
 * @param req                  请求对象
 * @param resp                 响应对象
 * @param model                数据体(应该是,里面是Map,key为数据名,value为数据体,最后通过装载到request对象转发出去)
 * @return 参数列表 Object[] args
 */
public Object[] handlerParameterArgs(List<ParameterDefinition> parameterDefinitions, HttpServletRequest req, HttpServletResponse resp, Model model) throws ClassNotFoundException {
    if (parameterDefinitions == null) {
        return null;
    }

    //实际参数的列表
    Object[] args = new Object[parameterDefinitions.size()];

    //将请求中的参数添加到args中
    for (ParameterDefinition parameterDefinition : parameterDefinitions) {
        String name = parameterDefinition.getParameterName();//参数名
        Class<?> type = parameterDefinition.getType();//参数类型
        int index = parameterDefinition.getIndex();//参数下标

        if (judgeTypeIsJavaOrNot(type)) {//常见数据类型
            handlerJavaType(req, name, type, args, index);
        } else if (type == HttpServletRequest.class) {//请求类型
            args[index] = req;
        } else if (type == HttpServletResponse.class) {//相应类型
            args[index] = resp;
        } else if (type == String[].class) {//数组类型
            String[] parameterValues = req.getParameterValues(name);
            args[index] = parameterValues;
        } else if (type == List.class) {//集合类型
            handlerListType(parameterDefinition, req, args, index);
        } else if (type == Model.class) {//Model类型
            args[index] = model;
        } else {//自定义类型
            handlerOtherType(parameterDefinition, req, args, index);
        }
    }
    return args;
}
judgeTypeIsJavaOrNot:判断方法参数是否是常见数据类型

常见参数类型:八大基本数据类型及其包装类 + String

private static final Class<?>[] COMMON_CLASSES = new Class[]{
            byte.class, short.class, int.class, long.class, float.class, double.class, char.class,
            Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, Character.class,
            String.class
    };
/**
 * 判断参数的类型是不是常见类型 [COMMON_CLASSES]
 *
 * @param type 参数类型
 * @return 是否为常见类型
 */
public boolean judgeTypeIsJavaOrNot(Class<?> type) {
    for (Class<?> clazz : COMMON_CLASSES) {
        if (type == clazz) {
            return true;
        }
    }
    return false;
}
handlerJavaType:处理常见参数
/**
 * 处理不同的常见数据类型,将其转化为对应的数据并添加到参数列表对应的位置
 *
 * @param req   请求对象
 * @param name  字段名
 * @param type  字段类型
 * @param args  参数列表
 * @param index 参数在方法中的下标
 */
public void handlerJavaType(HttpServletRequest req, String name, Class<?> type, Object[] args, int index) {
    String parameter = req.getParameter(name);
    if (type == byte.class || type == Byte.class) {
        args[index] = Byte.parseByte(parameter);
    } else if (type == short.class || type == Short.class) {
        args[index] = Short.parseShort(parameter);
    } else if (type == int.class || type == Integer.class) {
        args[index] = Integer.parseInt(parameter);
    } else if (type == long.class || type == Long.class) {
        args[index] = Long.parseLong(parameter);
    } else if (type == float.class || type == Float.class) {
        args[index] = Float.parseFloat(parameter);
    } else if (type == double.class || type == Double.class) {
        args[index] = Double.parseDouble(parameter);
    } else if (type == char.class || type == Character.class) {
        args[index] = parameter.toCharArray()[0];
    } else if (type == boolean.class || type == Boolean.class) {
        args[index] = Boolean.parseBoolean(parameter);
    }
    if (type == String.class) {
        args[index] = parameter;
    }
}
handlerListType:处理List参数
/**
 * 处理方法的参数是集合类型的方法,如果参数是List集合,那么要将List中的泛型取出来并设置对应的属性
 * 最后将泛型对应的对象添加到List中,再将List添加到参数列表中
 *
 * @param parameterDefinition 参数描述对象(包含泛型的类型)
 * @param req                 请求对象
 * @param args                参数列表
 * @param index               参数对应的下标
 */
public void handlerListType(ParameterDefinition parameterDefinition, HttpServletRequest req, Object[] args, int index) throws ClassNotFoundException {
    Type[] types = parameterDefinition.getTypes();//参数的泛型列表
    Type genericType = types[0];//泛型列表
    String typeName = genericType.getTypeName();//泛型的名称 cn.cnmd.pojo.User

    List<Object> list = new ArrayList<>();
    Map<String, String[]> parameterMap = req.getParameterMap();
    Set<Map.Entry<String, String[]>> entries = parameterMap.entrySet();
    for (Map.Entry<String, String[]> entry : entries) {
        String key = entry.getKey();
        String fieldValue = entry.getValue()[0];
        int i = Integer.parseInt(key.substring(key.indexOf("[") + 1, key.indexOf("]")));
        String fieldName = key.substring(key.indexOf(".") + 1);

        Class<?> aClass = Class.forName(typeName);
        Object o = null;
        try {
            o = list.get(i);
        } catch (IndexOutOfBoundsException e) {//该集合下标上没有元素
            try {
                o = aClass.newInstance();//创建对象
                list.add(o);
            } catch (InstantiationException | IllegalAccessException ex) {
                throw new RuntimeException(ex);
            }
        }
        try {
            BeanUtils.setProperty(o, fieldName, fieldValue);
        } catch (IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }
    args[index] = list;
}
handlerOtherType:处理自定义参数
/**
 * 处理自定义类型
 *
 * @param parameterDefinition 参数描述对象
 * @param req                 请求对象
 * @param args                参数列表
 * @param index               参数下标
 */
public void handlerOtherType(ParameterDefinition parameterDefinition, HttpServletRequest req, Object[] args, int index) {
    try {
        Object obj;
        //如果参数上带有@RequestBody注解则会将JSON字符串转化为对象
        if (parameterDefinition.isRequestBodyHasOrNot()) {
            BufferedReader reader = req.getReader();
            StringBuffer sb = new StringBuffer();
            char[] cs = new char[1024];
            int len;
            while ((len = reader.read(cs)) != -1) {
                sb.append(cs, 0, len);
            }
            //String --> Object
            obj = objectMapper.readValue(sb.toString(), parameterDefinition.getType());
        } else { //如果不带@RequestBody注解,则正常当作自定义对象处理
            obj = parameterDefinition.getType().newInstance();
            Map<String, String[]> parameterMap = req.getParameterMap();
            BeanUtils.populate(obj, parameterMap);
        }
        args[index] = obj;
    } catch (InstantiationException | IllegalAccessException | InvocationTargetException | IOException e) {
        throw new RuntimeException(e);
    }
}
handlerRequestVal:将Model中的k-v添加到request请求体中
/**
 * 将model数据对象装载到request对象中
 *
 * @param request 请求体对象
 * @param map     model数据对象的map(k-v存储了需要传递给前端的数据)
 */
public void handlerRequestVal(HttpServletRequest request, Map<String, Object> map) {
    Set<Map.Entry<String, Object>> entries = map.entrySet();
    for (Map.Entry<String, Object> entry : entries) {
        String key = entry.getKey();
        Object value = entry.getValue();
        request.setAttribute(key, value);
    }
}
handlerReturnVal:返回响应的总调度(按照方法的返回类型)
  1. 如果是String类型,说明返回值是一个URI,ps:‘/user/index.jsp’,就将数据加载到request对象中,跳转到该页面
  2. 如果是ModelAndView类型,说明返回值是一个ModelAndView对象[ Model数据体对象 + URI],再将数据加载到request对象中,通过ModelANdView.getViewName()跳转
  3. 如果是JSON数据[方法上带有@ResponseBody注解],就直接将数据发送到前端
/**
 * 处理返回值的函数,通过判断URI调用对应的方法的返回值类型,选择不同返回逻辑
 * 1.如果是String类型,说明返回值是一个URI,ps:'/user/index.jsp',就将数据加载到request对象中,跳转到该页面
 * 2.如果是ModelAndView类型,说明返回值是一个ModelAndView对象[ Model数据体对象 + URI],再将数据加载到request对象中,通过ModelANdView.getViewName()跳转
 * 3.如果是JSON数据[方法上带有@ResponseBody注解],就直接将数据发送到前端
 *
 * @param methodDefinition 方法描述对象
 * @param returnVal        调用方法的返回值对象
 * @param request          请求体对象
 * @param response         响应体对象
 * @param model            数据体对象
 */
public void handlerReturnVal(MethodDefinition methodDefinition, Object returnVal, HttpServletRequest request, HttpServletResponse response, Model model) throws ServletException, IOException {

    //如果返回的是一个String, 代表直接跳转,ps:'user/login2.action'
    if (returnVal.getClass() == String.class) {

        handlerRequestVal(request, model.getMap());
        jumpPage(returnVal, request, response);

        //如果返回的是ModelAndView对象,那么代表跳转的地址作为属性被注入到ModelAndView对象中,通过modelAndView.getViewName()跳转
    } else if (returnVal.getClass() == ModelAndView.class) {
        ModelAndView modelAndView = (ModelAndView) returnVal;

        handlerRequestVal(request, modelAndView.getMap());
        jumpPage(modelAndView.getViewName(), request, response);

        //如果这个方法上有@REsponseBody注解,则直接将returnVal转化为JSON字符串传出
    } else if (methodDefinition.isResponseBodyHasOrNot()) {

        String jsonStr = objectMapper.writeValueAsString(returnVal);
        sendResponse(jsonStr, response);
    }

}
jumpPage:跳转页面
/**
 * 页面跳转函数
 *
 * @param uri      需要跳转的URI
 * @param request  请求体对象(里面携带了从model装载的数据)
 * @param response 相应体对象
 */
public void jumpPage(Object uri, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String view = (String) uri;
    request.getRequestDispatcher(view).forward(request, response);
}
sendResponse:直接返回响应
/**
 * 向前端发送JSON数据
 * @param jsonStr JSON字符串
 * @param response 相应提对象
 */
public void sendResponse( String jsonStr,HttpServletResponse response) throws IOException {
    response.getWriter().write(jsonStr);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1870454.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于esp-idf的arm2d移植

什么是ARM2D Arm在Github上发布了一个专门针对“全体” Cortex-M处理器的2D图形加速库——Arm-2D 我们可以简单的把这个2D图形加速库理解为是一个专门针对Cortex-M处理器的标准“显卡驱动”。虽然这里的“显卡驱动”只是一个夸张的说法——似乎没有哪个Cortex-M处理器“配得上…

记一次对ouija渗透测试c语言逆向学习

概要 初始知识 web应用枚举 二进制逆向 文件枚举 堆栈溢出 学到知识 hash长度攻击 任意文件读取 二进制逆向分析 信息收集 端口扫描 nmap --min-rate 1000 -p- 10.129.30.104 发现22&#xff0c;80&#xff0c;3000端口 网站探测 目录枚举 feroxbuster -u http://10.1…

Qt 基于FFmpeg的视频播放器 - 播放、暂停以及拖动滑动条跳转

Qt 基于FFmpeg的视频转换器 - 播放、暂停以及拖动进度条跳转 引言一、设计思路二、核心源码以及相关参考链接 引言 本文基于FFmpeg&#xff0c;使用Qt制作了一个极简的视频播放器. 相比之前的版本&#xff0c;加入了播放、暂停、拖动滑动条跳转功能&#xff0c;如上所示 (左图)…

局域网聊天软件 matrix

窝有 3 只 Android 手机 (3 号手机, 6 号手机, 9 号手机), 2 台 ArchLinux PC (4 号 PC, 6 号 PC), 1 台 Fedora CoreOS 服务器 (5 号). (作为穷人, 窝使用的基本上是老旧的二手设备, 比如 5 年前的手机, 9 年前的笔记本, 10 年前的古老 e5v3 主机, 都比较便宜. ) 窝经常需要 …

format()函数

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 语法介绍 format()可以对数据进行格式化处理操作&#xff0c;语法如下&#xff1a; format(value, format_spec) format_spec为格式化解释。当参数…

高性能Web服务器-Nginx的常用模块

文章目录 Nginx安装Nginx平滑升级与回滚平滑升级流程第1步&#xff0c;下载新版本第2步&#xff0c;编译第3步&#xff0c;执行make第4步&#xff0c;对比新旧版本第5步&#xff0c;备份旧nginx二进制文件第6步&#xff0c;模拟用户正在访问nginx第7步&#xff0c;替换旧的ngin…

The First Descendant第一后裔联机失败、联机报错这样处理

第一后裔/The First Descendant是一款免费的多人合作射击游戏&#xff0c;玩家将进入一片混乱的英格里斯大陆&#xff0c;扮演继承者后裔&#xff0c;通过各种主支线任务和故事剧情触发&#xff0c;最终揭开自身的秘密&#xff0c;并带领大家一起抵抗邪恶势力的入侵。为了避免玩…

Flume学习

Flume(分布式数据采集系统)学习 1.Flume架构 什么是flume&#xff1f; flume是一个分布式、可靠、和高可用的海量日志采集、聚合和传输的系统。 支持在日志系统中定制各类数据发送方&#xff0c;用于收集数据; 同时&#xff0c;Flume提供对数据进行简单处理&#xff0c;并写到…

华为昇腾310B1芯片DVPP模块VENC视频编码接口调用流程以及视频编码代码梳理

目录 1 接口调用流程 2 代码流程梳理 1 接口调用流程 在CANN 8.0.RC1 AscendCL应用软件开发指南 (C&C, 推理) 01.pdf 文档中有接口调用流程 2 代码流程梳理 代码在samples: CANN Samples - Gitee.com 然后我把这个代码完整的看了一遍&#xff0c;然后梳理了详细的代码…

web学习笔记(七十二)

目录 1.vue2通过$parent实现组件传值——父传子 2.vue2 通过$children实现组件传值——子传父 3. provide和inject传值&#xff08;依赖注入&#xff09; 4.vue2如何操作dom 5.vue2如何拿到最新的dom 6.filters过滤器 7.vue2的生命周期 8.vuex的用法 1.vue2通过$parent…

【SCI索引,Fellow主讲】2024年可持续发展与能源资源国际学术会议(SDER 2024,8月9-11)

2024年可持续发展与能源资源国际学术会议&#xff08;SDER 2024&#xff09;将在2024年8月9-11日于中国重庆召开。 大会旨在为从事可持续发展与能源资源方面的专家学者、工程技术人员、技术研发人员提供一个共享科研成果和前沿技术&#xff0c;了解学术发展趋势&#xff0c;拓…

2.4G特技翻斗车方案定制

遥控翻斗车不仅能够提供基本的前进、后退、左转和右转功能&#xff0c;还设计有多种特技动作和互动模式&#xff0c;以增加娱乐性和互动性。 1、无线遥控&#xff1a;玩具翻斗车一般通过2.4G无线遥控器进行控制&#xff0c;允许操作者在一定距离内远程操控车辆。 2、炫彩灯光…

Java程序员接单的十条“野路子”,分分钟收入20K!

Java程序员除了主业工作外&#xff0c;也要适当扩展兼职接单这条路。毕竟Java接单可以说是Java程序员进行技术变现的最佳方式之一。 因为Java程序员兼职接单的难度相对更低&#xff0c;单量也比较可观&#xff0c;最重要的是性价比也很顶&#xff0c;且听我一一道来&#xff1a…

Nature推荐的三种ChatGPT论文写作指令(含PDF下载)

1. 润色学术论文 ChatGPT学术润色指令&#xff1a; “I’m writing a paper on [topic]for a leading [discipline] academic journal. WhatItried to say in the following section is [specific point]. Please rephrase itfor clarity, coherence and conciseness, ensuri…

Charles抓包工具系列文章(五)-- DNS spoofing (DNS域名伪装)

一、背景 DNS域名是依赖DNS域名服务器&#xff0c;特别是内部域名&#xff0c;最后寻址到后端服务地址。 当我们无法修改客户端的域名&#xff0c;而想让其指向到我们期望地址时&#xff0c;可以采用charles的DNS spoofing。 何谓DNS 欺骗&#xff1a;将自己的主机名指定给远…

电商平台数据功能封装API需要注意些什么?如何调用封装后的API?

一、引言 随着电商行业的蓬勃发展&#xff0c;电商平台的数据功能愈发复杂多样&#xff0c;如何高效、安全地管理和使用这些数据成为了电商平台开发者面临的重要问题。API&#xff08;Application Programming Interface&#xff09;作为不同软件之间进行通信的桥梁&#xff0…

Win32消息机制原理及消息运转

一.消息机制原理 1.消息类型&#xff1a; WIndows定义的一系列WM_XXX开头的&#xff0c;用来表示键盘按键&#xff0c;鼠标点击&#xff0c;窗口变化&#xff0c;用户自定义等各种消息; 2.消息队列&#xff1a; Windows为每一个正在运行的程序维护一个消息队列应用程序的消…

Pycharm 文件标头设置

一、设置模板步骤&#xff1a; “文件File--设置Settings--编辑器Editor--File and Code Templates- Python Script” 里面设置模板 官方预设变量表 变量名 含义 ${DATE} 当前系统日期 ${DAY} 当前月的第几日 ${DAY_NAME_SHORT} 当前星期几的单词缩写&#xff08…

计算机网络之数据通信原理(下)

上一讲内容&#xff1a;数据传输方式、数据传输形式、传输差错处理、常用差错检测方法 数据通信过程中&#xff0c;一个很重要的问题就是如何控制数据的传输&#xff0c;就涉及到了传输控制规程&#xff08;协议&#xff09; 下面介绍两种&#xff1a; ①BSC&#xff1a;面向…

java基于ssm+jsp 弹幕视频网站

1前台首页功能模块 弹幕视频网站&#xff0c;在弹幕视频网站可以查看首页、视频信息、商品信息、论坛信息、我的、跳转到后台、购物车、客服等内容&#xff0c;如图1所示。 图1前台首页界面图 登录&#xff0c;通过登录填写账号、密码等信息进行登录操作&#xff0c;如图2所示…