种地 发表于 2023-2-13 22:08:26

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

SpringMVC底层机制简单实现-04

https://github.com/liyuelian/springmvc-demo.git
8.任务7-完成简单视图解析

功能说明:通过目标方法返回的 String,转发或重定向到指定页面
8.1分析

原生的 SpringMVC 使用视图解析器来对 Handler 方法返回的 String(该String会转为视图类)进行解析,然后转发或重定向到指定页面。
这里为了简化,直接在自定义的前端控制器编写方法完成视图解析器的功能。
https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20230212213432844.png
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,根据形参数组的信息,将实参填充到实参数组中

            //步骤一:将方法的 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(":"); // forward或redirect
                  String viewPage = viewName.split(":"); // 要跳转的页面名
                  //判断是 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
https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/SpringMVC%E5%BA%95%E5%B1%82%E6%9C%BA%E5%88%B6%E5%AE%9E%E7%8E%B0%E4%B9%8B%E7%AE%80%E5%8D%95%E8%A7%86%E5%9B%BE%E8%A7%A3%E6%9E%90%E6%B5%8B%E8%AF%95.gif测试成功。
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,根据形参数组的信息,将实参填充到实参数组中
            //...
            //...
            //...
            //...
      
            //反射调用目标方法
            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,返回如下结果,测试成功。
https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/image-20230213200853614.png10.小结

https://liyuelian.oss-cn-shenzhen.aliyuncs.com/imgs/%E8%87%AA%E5%B7%B1%E5%AE%9E%E7%8E%B0SpringMVC%E5%BA%95%E5%B1%82%E6%9C%BA%E5%88%B6.png
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 格式的数据等



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: day08-SpringMVC底层机制简单实现-04