手写SpringMVC之调度器DispatcherServlet

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);
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C语言 | Leetcode C语言题解之第201题数字范围按位与

题目&#xff1a; 题解&#xff1a; int rangeBitwiseAnd(int m, int n) {while (m < n) {// 抹去最右边的 1n & (n - 1);}return n; }

vue3 【提效】自动导入框架方法 unplugin-auto-import 实用教程

是否还在为每次都需要导入框架方法而烦恼呢&#xff1f; // 每次都需手动导入框架方法 import { ref } from vuelet num ref(0)用 unplugin-auto-import 来帮你吧&#xff0c;以后只需这样写就行啦&#xff01; let num ref(0)官方示例如下图 使用流程 1. 安装 unplugin-au…

网信办算法备案详细解读——中国人工智能监管新规

中国出台新规旨在防范人工智能的相关风险&#xff0c;且规定了从事人工智能相关业务的实体的合规义务。 要点&#xff1a; • 中华人民共和国&#xff08;中国&#xff09; 通过推出并实施如下一系列法规&#xff0c;在人工智能监管方面领先于其他司法管辖 区&#xff1a…

ONLYOFFICE 8.1编辑器桌面应用程序来袭——在线全面测评

目录 ✈下载✈ &#x1f440;界面&#x1f440; &#x1f44a;功能&#x1f44a; &#x1f9e0;幻灯片版式的重大改进&#x1f9e0; ✂无缝切换文档编辑、审阅和查看模式✂ &#x1f3b5;在演示文稿中播放视频和音频文件&#x1f3b5; &#x1f917;版本 8.1&#xff1a…

Web渗透:文件包含漏洞

Ⅱ.远程文件包含 远程文件包含漏洞&#xff08;Remote File Inclusion, RFI&#xff09;是一种Web应用程序漏洞&#xff0c;允许攻击者通过URL从远程服务器包含并执行文件&#xff1b;RFI漏洞通常出现在动态包含文件的功能中&#xff0c;且用户输入未经适当验证和过滤。接着我…

亚马逊AI技术风波:人工智能“洗白”现象引发质疑

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

并发 多线程

目录 thread thread 类总览 构造函数 join joinable ​编辑 detach swap yield swap 成员函数的调用 namespace::this_thread 线程同步--锁 互斥锁mutex 递归锁recursive_mutex 定时锁 Lock 锁辅助类 lock_guard​编辑 unique_lock std::lock 解决死锁问题 消息…

提升Unity WebGL游戏启动速度

一、查看启动耗时 通过修改unity-namespace.js中hideTimeLogModal为false&#xff0c;显示timelog开发者可以看到小游戏目前的启动首屏时长&#xff1a; 将其设置为false后&#xff0c;启动小程序后就会显示启动耗时 要知道各个阶段的含义&#xff0c;我们必要理解启动流程。 …

vue3用自定义指令实现按钮权限

1&#xff0c;编写permission.ts文件 在src/utils/permission.ts import type { Directive } from "vue"; export const permission:Directive{// 在绑定元素的父组件被挂载后调用mounted(el,binding){// el&#xff1a;指令所绑定的元素&#xff0c;可以用来直接操…

小程序消息定时任务(定时触发器)发送总结

文章目录 小程序消息定时任务&#xff08;定时触发器&#xff09;发送总结1.开发思路2.实现办法3.查看定时触发器是否正常运作4.总结 小程序消息定时任务&#xff08;定时触发器&#xff09;发送总结 1.开发思路 在使用小程序的时候总是会遇到消息任务发送的情况&#xff0c;…

BERT论文略读

《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》 &#xff08;https://arxiv.org/abs/1810.04805&#xff09; 摘要&#xff1a;前人优秀工作仅用了单向信息且不能很好的应用到各类下游任务&#xff0c;本文提出一种基于Transformer的双…

三种分布式锁实现方式

目录 1、数据库自增 2、Redis自增 3、Zookeeper 4、其他 4.1、雪花算法 4.2、Tinyid 4.3、Leaf 4.4、数据库号段 1、数据库自增 利用数据库表的自增特性&#xff0c;或主键唯一性&#xff0c;实现分布式ID REPLACE INTO id_table (stub) values (’a‘) ; SELECT LA…

4A的「A」会变成AI的「A」吗?

戛纳国际创意节上&#xff0c;广告集团WPP的全球CEO Mark Read 和英国CEO Karen Blackett 解释了WPP如何应对AIGC所带来的「威胁」。同时&#xff0c;Mark Read 与Elon Musk对话&#xff0c;讨论「技术创新的变革力量&#xff0c;人工智能如何重塑创造力、商业和社会&#xff0…

Ueditor中集成135编辑器

一、背景 在资讯项目平台运营过程中&#xff0c;资讯需要排版&#xff0c;一般都是在135编辑器排好以后&#xff0c;复制到平台中UEditor编辑器中&#xff0c;所以&#xff0c;他们建议集成一下135哈 二、了解135编辑器 开始调研了解135编辑器&#xff0c;发现人家就支持集成…

PVE 8.2.2安装OpenWrt 23.05.3

1,下载官方openwrt 23.5.3镜像并解压 2&#xff0c;进入pve上传镜像 复制这段文字之后需要使用 创建虚拟机 删除磁盘 安装完毕后 shell 运行 qm importdisk 100 /var/lib/vz/template/iso/openwrt-23.05.3-x86-64-generic-ext4-combined-efi.img local-lvm 其中100是虚拟…

mac菜单栏应用管理软件:Bartender 4 for Mac 中文激活版

Bartender 4 是一款由Bearded Men Games开发的适用于Mac操作系统的应用程序&#xff0c;它被设计用来优化和美化Mac菜单栏的功能。自从macOS Big Sur开始&#xff0c;Mac的菜单栏可以自定义&#xff0c;用户可以添加和移除各种图标。Bartender 4就是在这个背景下应运而生&#…

Spring Boot中获取请求参数的几种方式

前言 在构建现代 Web 应用时&#xff0c;处理来自客户端的请求参数是不可或缺的一部分。Spring Boot作为构建微服务应用的领先框架&#xff0c;提供了多种灵活高效的方式来获取请求参数&#xff0c;满足各种应用场景。 无论您是Spring Boot的初学者&#xff0c;还是希望更深入…

[分布式网络通讯框架]----Protobuf安装配置--附带每一步截图

Protobuf Protobuf&#xff08;Protocol Buffers&#xff09;协议是一种由 Google 开发的二进制序列化格式和相关的技术&#xff0c;它用于高效地序列化和反序列化结构化数据&#xff0c;通常用于网络通信、数据存储等场景。 为什么要使用Protobuf Protobuf 在许多领域都得到…

【PL理论深化】(8) Ocaml 语言:元组和列表 | 访问元组中的元素 | 列表中的 head 和 tail | 基本列表操作符

&#x1f4ac; 写在前面&#xff1a;本章我们将探讨 OCaml 中的元组&#xff08;tuple&#xff09;和列表&#xff08;list&#xff09;&#xff0c;它们是函数式编程语言中最常用的数据结构。 目录 0x00 元组&#xff08;Tuple&#xff09; 0x01 访问元组中的元素 0x02 列表&…

电脑开机之后,键盘鼠标需要重新插拔才能正常使用?

前言 小白平时修电脑修得多&#xff0c;总是会遇到各种各样的奇葩问题。这不&#xff0c;又有一位小伙伴来咨询&#xff1a;电脑开机之后&#xff0c;键盘鼠标都不能用&#xff0c;需要重新插拔一下才能正常使用。 啧啧啧&#xff0c;真的是很奇怪的问题&#xff0c;基本上没见…