SpringMVC底层机制简单实现-04

https://github.com/liyuelian/springmvc-demo.git

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机制梳理

  1. web.xml 中配置前端控制器(DispatcherServlet)和 spring 容器文件

  2. 当启动 tomcat 时,DispatcherServlet 被 tomcat 创建

  3. 前端控制器工作:

    • (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的更多相关文章

  1. day05-SpringMVC底层机制简单实现-01

    SpringMVC底层机制简单实现-01 主要完成:核心分发控制器+Controller和Service注入容器+对象自动装配+控制器方法获取参数+视图解析+返回JSON格式数据 1.搭建开发环境 创 ...

  2. tensorflow入门教程和底层机制简单解说——本质就是图计算,自动寻找依赖,想想spark机制就明白了

    简介 本章的目的是让你了解和运行 TensorFlow! 在开始之前, 让我们先看一段使用 Python API 撰写的 TensorFlow 示例代码, 让你对将要学习的内容有初步的印象. 这段很短 ...

  3. SpringMVC视图机制详解[附带源码分析]

    目录 前言 重要接口和类介绍 源码分析 编码自定义的ViewResolver 总结 参考资料 前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门bl ...

  4. 深入源码分析SpringMVC底层原理(二)

    原文链接:深入源码分析SpringMVC底层原理(二) 文章目录 深入分析SpringMVC请求处理过程 1. DispatcherServlet处理请求 1.1 寻找Handler 1.2 没有找到 ...

  5. SpringMVC异常处理机制

    SpringMVC异常处理机制 springMVC会将所有在doDispatch方法中的异常捕获,然后处理.无法处理的异常会抛出给容器处理. 在doDispatch()中调用processDispat ...

  6. [转]STL 容器一些底层机制

    1.vector 容器 vector 的数据安排以及操作方式,与 array 非常相似.两者的唯一区别在于空间的运用的灵活性.array 是静态空间,一旦配置了就不能改变,vector 是动态数组.在 ...

  7. C++ STL容器底层机制

    1.vector容器 vector的数据安排以及操作方式,与array非常相似.两者的唯一区别在于空间的运用的灵活性.array是静态空间,一旦配置了就不能改变.vector是动态空间,随着元素的加入 ...

  8. 探索C++的底层机制

    探索C++的底层机制 在看这篇文章之前,请你先要明白一点:那就是c++为我们所提供的各种存取控制仅仅是在编译阶段给我们的限制,也就是说是编译器确保了你在完成任务之前的正确行为,如果你的行为不正确,那么 ...

  9. 设计模式:与SpringMVC底层息息相关的适配器模式

    目录 前言 适配器模式 1.定义 2.UML类图 3.实战例子 4.总结 SpringMVC底层的适配器模式 参考 前言 适配器模式是最为普遍的设计模式之一,它不仅广泛应用于代码开发,在日常生活里也很 ...

  10. php-浅谈php底层机制

    php-浅谈php底层机制 1. PHP的设计理念及特点 多进程模型:由于PHP是多进程模型,不同请求间互不干涉,这样保证了一个请求挂掉不会对全盘服务造成影响,当然,随着时代发展,PHP也早已支持多线 ...

随机推荐

  1. 2022春每日一题:Day 32

    题目:[USACO12DEC]First! G 不太记得当时怎么想的了,但是显然,当一个字符串的前缀存在则他一定不是first,然后做法:对于每个字符串,把每个字符结尾跟他有相同前缀的单词的同元素建边 ...

  2. 配置MSTP功能示例

    组网需求 在一个复杂的网络中,网络规划者由于冗余备份的需要,一般都倾向于在设备之间部署多条物理链路,其中一条作主用链路,其他链路作备份.这样就难免会形成环形网络,若网络中存在环路,可能会引起广播风暴和 ...

  3. Fastjsonfan反序列化(一)

    前置知识 Fastjson 是一个 Java 库,可以将 Java 对象转换为 JSON 格式,当然它也可以将 JSON 字符串转换为 Java 对象. Fastjson 可以操作任何 Java 对象 ...

  4. <二>派生类的构造过程

    派生类从继承可以继承来所有的成员(变量和方法) 除了构造函数和析构函数 派生类怎么初始化从基类继承来的成员变量的呢?通过调用基类的构造函数来初始化 派生类的构造函数和析构函数,负责初始化和清理派生类部 ...

  5. Day24.1:抽象类的详解

    抽象类 1.1抽象类概述 一个动物类中,我们创建对象时会去new一个动物:但是我们不应该直接创建动物这个对象,因为动物本身就是抽象的,没有动物这种实例,我们创建的应该是一个具体的动物类,比如猫.狗等动 ...

  6. Referenced file contains errors (http://mybatis.org/dtd/mybatis-3-config.dtd). For more information, right click on the message in the Problems View and select "Show Details..."

    mybatis配置文件报错Referenced file contains errors mybatis的配置文件报错 The errors below were detected when vali ...

  7. Kettle:跨库(SQLServer->PostgreSQL)同步多张表数据的详细设计过程

    〇.参考地址 1.多个Excel实现同步 https://www.wangt.cc/2021/05/kettle%E5%A4%9A%E4%B8%AA%E8%A1%A8%E4%B8%80%E8%B5%B ...

  8. 看起来简单实际上却很牛的KMP算法:LeetCode572-另一棵树的子树

    题目描述 给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树.s 的一个子树包括 s 的一个节点和这个节点的所有子孙.s 也可以看做它自身的一棵子树. 暴力解法 从 ...

  9. STM32标准库中GPIO_ReadInputData与GPIO_ReadInputDataBit的区别

    GPIO_ReadInputData读的是GPIOx的整个IDR寄存器的数据,返回一个十六位数,对应IDR寄存器的十六位.反映GPIOx所有端口的电平状态,所以参数只用传入GPIOx. uint16_ ...

  10. 学会了selenium 模拟鼠标操作,你就可以偷懒点点点了

    前言 我们在做 Web 自动化的时候,有时候页面的元素不需要我们点击,值需要把鼠标移动上去就能展示各种信息. 这个时候我们可以通过操作鼠标来实现,接下来我们来讲一下使用 selenium 做 Web ...