day08-SpringMVC底层机制简单实现-04
SpringMVC底层机制简单实现-04
8.任务7-完成简单视图解析
功能说明:通过目标方法返回的 String,转发或重定向到指定页面
8.1分析
原生的 SpringMVC 使用视图解析器来对 Handler 方法返回的 String(该String会转为视图类)进行解析,然后转发或重定向到指定页面。
这里为了简化,直接在自定义的前端控制器编写方法完成视图解析器的功能。

8.2代码实现
(1)修改 MyDispatcherServlet 的 executeDispatch 方法
部分代码:
//编写方法,完成分发请求
private void executeDispatch(HttpServletRequest request, HttpServletResponse response) {
MyHandler myHandler = getMyHandler(request);
try {
//如果 myHandler为 null,说明请求 url没有匹配的方法,即用户请求的资源不存在
if (myHandler == null) {
response.getWriter().print("<h1>404 NOT FOUND</h1>");
} else {//匹配成功,就反射调用控制器的方法
//1.先获取目标方法的所有形参的参数信息
Class<?>[] parameterTypes = myHandler.getMethod().getParameterTypes();
//2.创建一个参数数组(对应实参数组),在后面反射调动目标方法时会用到
Object[] params = new Object[parameterTypes.length];
//遍历形参数组 parameterTypes,根据形参数组的信息,将实参填充到实参数组中
//步骤一:将方法的 request 和 response 参数封装到参数数组,进行反射调用
for (int i = 0; i < parameterTypes.length; i++) {
//....
//....略
//....
}
//步骤二:将 http请求的参数封装到 params数组中[要注意填充实参数组的顺序问题]
//先处理中文乱码问题
request.setCharacterEncoding("utf-8");
Map<String, String[]> parameterMap = request.getParameterMap();
// 遍历 parameterMap,将请求参数按照顺序填充到实参数组 params
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
//....
//....略
//....
}
//反射调用目标方法
Object result = myHandler.getMethod()
.invoke(myHandler.getController(), params);
//对返回的结果进行解析(原生的SpringMVC通过视图解析器来完成)
if (result instanceof String) {
String viewName = (String) result;
System.out.println("viewName=" + viewName);
if (viewName.contains(":")) {//如果返回的String结果为 forward:/login_ok.jsp
// 或 redirect:/login_ok.jsp 的形式
String viewType = viewName.split(":")[0]; // forward或redirect
String viewPage = viewName.split(":")[1]; // 要跳转的页面名
//判断是 forward 还是 redirect
if ("forward".equals(viewType)) {//请求转发
request.getRequestDispatcher(viewPage)
.forward(request, response);
} else if ("redirect".equals(viewType)) {//重定向
//注意这里的路径问题
viewPage = request.getContextPath() + viewPage;
response.sendRedirect(viewPage);
}
} else {//如果两者都没有,默认为请求转发
request.getRequestDispatcher("/" + viewName)
.forward(request, response);
}
}//这里还可以拓展
}
} catch (Exception e) {
e.printStackTrace();
}
}
(2)创建测试页面和测试方法
MonsterService 接口:
package com.li.service;
import com.li.entity.Monster;
import java.util.List;
/**
* @author 李
* @version 1.0
*/
public interface MonsterService {
//增加方法,处理登录
public boolean login(String name);
}
MonsterServiceImpl 实现类:
package com.li.service.impl;
import com.li.entity.Monster;
import com.li.myspringmvc.annotation.Service;
import com.li.service.MonsterService;
import java.util.ArrayList;
import java.util.List;
/**
* @author 李
* @version 1.0
* MonsterServiceImpl 作为一个Service对象注入容器
*/
@Service
public class MonsterServiceImpl implements MonsterService {
@Override
public boolean login(String name) {
//模拟DB
if ("白骨精".equals(name)) {
return true;
} else {
return false;
}
}
}
MonsterController 控制器:
package com.li.controller;
import com.li.entity.Monster;
import com.li.myspringmvc.annotation.AutoWired;
import com.li.myspringmvc.annotation.Controller;
import com.li.myspringmvc.annotation.RequestMapping;
import com.li.myspringmvc.annotation.RequestParam;
import com.li.service.MonsterService;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
/**
* @author 李
* @version 1.0
* 用于测试的 Controller
*/
@Controller
public class MonsterController {
//属性
@AutoWired
private MonsterService monsterService;
//处理登录的方法,返回要请求转发或重定向的字符串
@RequestMapping(value = "/monster/login")
public String login(HttpServletRequest request,
HttpServletResponse response,
@RequestParam(value = "monsterName") String mName) {
System.out.println("----接收到的mName-->" + mName);
request.setAttribute("mName", mName);
boolean b = monsterService.login(mName);
if (b) {//登录成功
// 请求转发到login_ok.jsp
//return "forward:/login_ok.jsp";
//return "redirect:/login_ok.jsp";
return "login_ok.jsp";
} else {//登录失败
//return "forward:/login_error.jsp";
//return "redirect:/login_error.jsp";
return "login_error.jsp";
}
}
}
在webapp目录下分别创建 login.jsp,login_ok.jsp,login_error.jsp
login.jsp:
<%--
Created by IntelliJ IDEA.
User: li
Date: 2023/2/12
Time: 22:24
Version: 1.0
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录页面</title>
</head>
<body>
<h1>登录页面</h1>
<form action="monster/login" method="post">
妖怪名:<input type="text" name="monsterName"><br/>
<input type="submit" value="登录">
</form>
</body>
</html>
login_ok.jsp:
<%--
Created by IntelliJ IDEA.
User: li
Date: 2023/2/12
Time: 22:27
Version: 1.0
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
<title>登录成功</title>
</head>
<body>
<h1>登录成功</h1>
欢迎你:${requestScope.mName}
</body>
</html>
login_error.jsp:
<%--
Created by IntelliJ IDEA.
User: li
Date: 2023/2/12
Time: 22:28
Version: 1.0
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
<title>登录失败</title>
</head>
<body>
<h1>登录失败</h1>
sorry,登录失败 ${requestScope.mName}
</body>
</html>
(3)启动 tomcat,访问 http://localhost:8080/li_springmvc/login.jsp

测试成功。
9.任务8-自定义@ResponseBody
9.1分析
功能说明:通过自定义@ResponseBody 注解,返回 JSON格式数据
在实际开发中,前后端分离的项目,通常是直接json数据给客户端/浏览器。客户端接收到数据后,再自己决定如何处理和显示。
9.2代码实现
(1)@ResponseBody 注解
package com.li.myspringmvc.annotation;
import java.lang.annotation.*;
/**
* @author 李
* @version 1.0
* ResponseBody 注解用于指定目标方法是否要返回指定格式的数据
* 如果value为默认值,或者value="json",认为目标方法要返回的数据格式为json
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody {
String value() default "";
}
(2)修改 MyDispatcherServlet 的 executeDispatch 方法
//编写方法,完成分发请求
private void executeDispatch(HttpServletRequest request, HttpServletResponse response) {
MyHandler myHandler = getMyHandler(request);
try {
//如果 myHandler为 null,说明请求 url没有匹配的方法,即用户请求的资源不存在
if (myHandler == null) {
response.getWriter().print("<h1>404 NOT FOUND</h1>");
} else {//匹配成功,就反射调用控制器的方法
Class<?>[] parameterTypes = myHandler.getMethod().getParameterTypes();
//2.创建一个参数数组(对应实参数组),在后面反射调动目标方法时会用到
Object[] params = new Object[parameterTypes.length];
//遍历形参数组 parameterTypes,根据形参数组的信息,将实参填充到实参数组中
//...
//...
//...
//...
//反射调用目标方法
Object result =
myHandler.getMethod().invoke(myHandler.getController(), params);
//对返回的结果进行解析(原生的SpringMVC通过视图解析器来完成)
if (result instanceof String) {
//....略
}//这里还可以拓展
else if (result instanceof ArrayList) {//如果是一个集合
Method method = myHandler.getMethod();
//判断目标方法是否有一个@ResponseBody注解
if (method.isAnnotationPresent(ResponseBody.class)) {
String valueType = method.getAnnotation(ResponseBody.class).value();
//如果注解的为默认值,或者value="json",就认为目标方法要返回的数据格式为json
if ("json".equals(valueType) || "".equals(valueType)) {
//对Arraylist转为json字符串
//这里我们使用jackson包下的工具类解决
ObjectMapper objectMapper = new ObjectMapper();
String resultJson = objectMapper.writeValueAsString(result);
//这里简单处理,就直接返回
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write(resultJson);
writer.flush();
writer.close();
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
(3)pom.xml文件中引入jackson
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.4</version>
</dependency>
(4)MonsterController 测试类增加方法测试
/**
* 编写方法,返回json格式的数据
* 1.目标方法返回的结果是给SpringMVC底层通过反射调用的位置
* 2.我们在SpringMVC底层反射调用的位置接收到结果并进行解析即可
* 3. @ResponseBody(value = "json") 表示希望以json格式返回数据给浏览器
* @param request
* @param response
* @return
*/
@RequestMapping(value = "/monster/list/json")
@ResponseBody(value = "json")
public List<Monster> listMonsterByJson(HttpServletRequest request,
HttpServletResponse response) {
List<Monster> monsters = monsterService.listMonster();
return monsters;
}
(5)启动 tomcat,浏览器访问 http://localhost:8080/li_springmvc/monster/list/json,返回如下结果,测试成功。

10.小结

SpringMVC机制梳理
web.xml 中配置前端控制器(DispatcherServlet)和 spring 容器文件
当启动 tomcat 时,DispatcherServlet 被 tomcat 创建
前端控制器工作:
(1)创建 spring 容器并初始化(从 web.xml 文件中获取 spring配置文件名):
a. 扫描包,获取要注入的类的全路径。
b. 将扫描到的类进行反射,放入ioc容器。
c. 完成属性自动装配
(2)记录控制器的目标方法和 url 的映射关系(在原生 SpringMVC 中,这个工作由 HandlerMapping 完成)
(3)完成分发请求:
a. 完成用户 url 和控制器 url 的匹配以及目标方法的调用
b. 目标方法参数的自动赋值:对浏览器请求 url 的参数进行处理,考虑目标方法形参的多样性,将其封装到参数数组,以反射调用的形式传递给目标方法
目标方法的实参是在 SpringMVC 底层通过封装好的参数数组传入的
c. 反射目标方法,对目标方法返回的结果进行解析(原生SpringMVC中,解析的工作由视图解析器完成),决定请求转发/重定向/返回 json 格式的数据等
day08-SpringMVC底层机制简单实现-04的更多相关文章
- day05-SpringMVC底层机制简单实现-01
SpringMVC底层机制简单实现-01 主要完成:核心分发控制器+Controller和Service注入容器+对象自动装配+控制器方法获取参数+视图解析+返回JSON格式数据 1.搭建开发环境 创 ...
- tensorflow入门教程和底层机制简单解说——本质就是图计算,自动寻找依赖,想想spark机制就明白了
简介 本章的目的是让你了解和运行 TensorFlow! 在开始之前, 让我们先看一段使用 Python API 撰写的 TensorFlow 示例代码, 让你对将要学习的内容有初步的印象. 这段很短 ...
- SpringMVC视图机制详解[附带源码分析]
目录 前言 重要接口和类介绍 源码分析 编码自定义的ViewResolver 总结 参考资料 前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门bl ...
- 深入源码分析SpringMVC底层原理(二)
原文链接:深入源码分析SpringMVC底层原理(二) 文章目录 深入分析SpringMVC请求处理过程 1. DispatcherServlet处理请求 1.1 寻找Handler 1.2 没有找到 ...
- SpringMVC异常处理机制
SpringMVC异常处理机制 springMVC会将所有在doDispatch方法中的异常捕获,然后处理.无法处理的异常会抛出给容器处理. 在doDispatch()中调用processDispat ...
- [转]STL 容器一些底层机制
1.vector 容器 vector 的数据安排以及操作方式,与 array 非常相似.两者的唯一区别在于空间的运用的灵活性.array 是静态空间,一旦配置了就不能改变,vector 是动态数组.在 ...
- C++ STL容器底层机制
1.vector容器 vector的数据安排以及操作方式,与array非常相似.两者的唯一区别在于空间的运用的灵活性.array是静态空间,一旦配置了就不能改变.vector是动态空间,随着元素的加入 ...
- 探索C++的底层机制
探索C++的底层机制 在看这篇文章之前,请你先要明白一点:那就是c++为我们所提供的各种存取控制仅仅是在编译阶段给我们的限制,也就是说是编译器确保了你在完成任务之前的正确行为,如果你的行为不正确,那么 ...
- 设计模式:与SpringMVC底层息息相关的适配器模式
目录 前言 适配器模式 1.定义 2.UML类图 3.实战例子 4.总结 SpringMVC底层的适配器模式 参考 前言 适配器模式是最为普遍的设计模式之一,它不仅广泛应用于代码开发,在日常生活里也很 ...
- php-浅谈php底层机制
php-浅谈php底层机制 1. PHP的设计理念及特点 多进程模型:由于PHP是多进程模型,不同请求间互不干涉,这样保证了一个请求挂掉不会对全盘服务造成影响,当然,随着时代发展,PHP也早已支持多线 ...
随机推荐
- natapp内网穿透
NATAPP 官网地址 https://natapp.cn/ 下载 点击下载,选择符合自己的版本 注册 下载完成后解压是个natapp.exe程序,这里先不用动,回到官网首页 完成注册并登录,选择免费 ...
- 基于python的数学建模---传染病六个模型
六个模型的区别 SI-Model import scipy.integrate as spi import numpy as np import matplotlib.pyplot as plt # ...
- Linux 中的内部命令和外部命令
Linux 中的内部命令和外部命令 作者:Grey 原文地址: 博客园:Linux 中的内部命令和外部命令 CSDN:Linux 中的内部命令和外部命令 什么是 bash shell ? bash s ...
- Centos安装Nodejs简单方式
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时.本文主要讲的是如何在Linux即Centos上安装Nodejs的简单方式,有比设置环境变量更加简单的方式,那就是设 ...
- 【第7篇】AI语音交互原理介绍
本章主要介绍AI语音交互的原理,包括语音交互的流程以及各流程节点所涉及的相关知识,如语音采集.语音识别.自然语言处理.语音合成等. 2.1 AI语音交互 AI语音交互通俗点说就是人与机器间进行语音理解 ...
- 4.6:HBase操作实验
〇.概述 1.拓扑结构 2.目标 进行Hbase实验来熟悉Hbase的基本操作. 一.基本操作 1.启动进程 16610 2.连接集群 3.常见操作
- Agileboot 1.6.0 发布啦 - 一款致力于规范/精简/可维护 的Springboot + Vue3的快速开发脚手架
平台简介 AgileBoot是一套开源的全栈精简快速开发平台,毫无保留给个人及企业免费使用.本项目的目标是做一款精简可靠,代码风格优良,项目规范的小型开发脚手架. 适合个人开发者的小型项目或者公司内部 ...
- 持续发烧,聊聊Dart语言的静态编译,能挑战Go不?
前言 前两天写了几篇文章,谈了谈Dart做后端开发的优势,比如: <Dart开发服务端,我是不是发烧(骚)了?> <持续发烧,试试Dart语言的异步操作,效率提升500%> & ...
- IDEA引入本地jar包的几种方法
有时候,项目需要引入一些第三方的依赖,这时候,就需要导入这些jar包.以下分享两种方式: 方式一.使用IDEA程序引入jar包 1.首先,点他! 2.然后,点他! 3.再然后,点他! 4.最后,在这里 ...
- 真实世界的人工智能应用落地——OpenAI篇 ⛵
作者:韩信子@ShowMeAI 深度学习实战系列:https://www.showmeai.tech/tutorials/42 本文地址:https://www.showmeai.tech/artic ...