草庐IT

day08-SpringMVC底层机制简单实现-04

liyuelian 2023-04-19 原文

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. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  2. ruby - 简单获取法拉第超时 - 2

    有没有办法在这个简单的get方法中添加超时选项?我正在使用法拉第3.3。Faraday.get(url)四处寻找,我只能先发起连接后应用超时选项,然后应用超时选项。或者有什么简单的方法?这就是我现在正在做的:conn=Faraday.newresponse=conn.getdo|req|req.urlurlreq.options.timeout=2#2secondsend 最佳答案 试试这个:conn=Faraday.newdo|conn|conn.options.timeout=20endresponse=conn.get(url

  3. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

  4. ruby-on-rails - 简单的 Ruby on Rails 问题——如何将评论附加到用户和文章? - 2

    我意识到这可能是一个非常基本的问题,但我现在已经花了几天时间回过头来解决这个问题,但出于某种原因,Google就是没有帮助我。(我认为部分问题在于我是一个初学者,我不知道该问什么......)我也看过O'Reilly的RubyCookbook和RailsAPI,但我仍然停留在这个问题上.我找到了一些关于多态关系的信息,但它似乎不是我需要的(尽管如果我错了请告诉我)。我正在尝试调整MichaelHartl'stutorial创建一个包含用户、文章和评论的博客应用程序(不使用脚手架)。我希望评论既属于用户又属于文章。我的主要问题是:我不知道如何将当前文章的ID放入评论Controller。

  5. ruby - 使用 Ruby 通过 Outlook 发送消息的最简单方法是什么? - 2

    我的工作要求我为某些测试自动生成电子邮件。我一直在四处寻找,但未能找到可以快速实现的合理解决方案。它需要在outlook而不是其他邮件服务器中,因为我们有一些奇怪的身份验证规则,我们需要保存草稿而不是仅仅发送邮件的选项。显然win32ole可以做到这一点,但我找不到任何相当简单的例子。 最佳答案 假设存储了Outlook凭据并且您设置为自动登录到Outlook,WIN32OLE可以很好地完成此操作:require'win32ole'outlook=WIN32OLE.new('Outlook.Application')message=

  6. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  7. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  8. postman——集合——执行集合——测试脚本——pm对象简单示例02 - 2

    //1.验证返回状态码是否是200pm.test("Statuscodeis200",function(){pm.response.to.have.status(200);});//2.验证返回body内是否含有某个值pm.test("Bodymatchesstring",function(){pm.expect(pm.response.text()).to.include("string_you_want_to_search");});//3.验证某个返回值是否是100pm.test("Yourtestname",function(){varjsonData=pm.response.json

  9. Qt Designer的简单使用 - 2

    在前面两节的例子中,主界面窗口的尺寸和标签控件显示的矩形区域等,都是用C++代码编写的。窗口和控件的尺寸都是预估的,控件如果多起来,那就不好估计每个控件合适的位置和大小了。用C++代码编写图形界面的问题就是不直观,因此Qt项目开发了专门的可视化图形界面编辑器——QtDesigner(Qt设计师)。通过QtDesigner就可以很方便地创建图形界面文件*.ui,然后将ui文件应用到源代码里面,做到“所见即所得”,大大方便了图形界面的设计。本节就演示一下QtDesigner的简单使用,学习拖拽控件和设置控件属性,并将ui文件应用到Qt程序代码里。使用QtDesigner设计界面在开始菜单中找到「Q

  10. MIMO-OFDM无线通信技术及MATLAB实现(1)无线信道:传播和衰落 - 2

     MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO

随机推荐