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

种地  金牌会员 | 2023-2-13 22:08:26 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 559|帖子 559|积分 1677

SpringMVC底层机制简单实现-04

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

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

原生的 SpringMVC 使用视图解析器来对 Handler 方法返回的 String(该String会转为视图类)进行解析,然后转发或重定向到指定页面。
这里为了简化,直接在自定义的前端控制器编写方法完成视图解析器的功能。

8.2代码实现

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

9.1分析

功能说明:通过自定义@ResponseBody 注解,返回 JSON格式数据
在实际开发中,前后端分离的项目,通常是直接json数据给客户端/浏览器。客户端接收到数据后,再自己决定如何处理和显示。
9.2代码实现

(1)@ResponseBody 注解
  1. package com.li.myspringmvc.annotation;
  2. import java.lang.annotation.*;
  3. /**
  4. * @author 李
  5. * @version 1.0
  6. * ResponseBody 注解用于指定目标方法是否要返回指定格式的数据
  7. * 如果value为默认值,或者value="json",认为目标方法要返回的数据格式为json
  8. */
  9. @Target(ElementType.METHOD)
  10. @Retention(RetentionPolicy.RUNTIME)
  11. @Documented
  12. public @interface ResponseBody {
  13.     String value() default "";
  14. }
复制代码
(2)修改 MyDispatcherServlet 的 executeDispatch 方法
  1. //编写方法,完成分发请求
  2. private void executeDispatch(HttpServletRequest request, HttpServletResponse response) {
  3.     MyHandler myHandler = getMyHandler(request);
  4.     try {
  5.         //如果 myHandler为 null,说明请求 url没有匹配的方法,即用户请求的资源不存在
  6.         if (myHandler == null) {
  7.             response.getWriter().print("<h1>404 NOT FOUND</h1>");
  8.         } else {//匹配成功,就反射调用控制器的方法
  9.             Class<?>[] parameterTypes = myHandler.getMethod().getParameterTypes();
  10.             //2.创建一个参数数组(对应实参数组),在后面反射调动目标方法时会用到
  11.             Object[] params = new Object[parameterTypes.length];
  12.             //遍历形参数组 parameterTypes,根据形参数组的信息,将实参填充到实参数组中
  13.             //...
  14.             //...
  15.             //...
  16.             //...
  17.         
  18.             //反射调用目标方法
  19.             Object result =
  20.                 myHandler.getMethod().invoke(myHandler.getController(), params);
  21.             //对返回的结果进行解析(原生的SpringMVC通过视图解析器来完成)
  22.             if (result instanceof String) {
  23.               //....略
  24.             }//这里还可以拓展
  25.             else if (result instanceof ArrayList) {//如果是一个集合
  26.                 Method method = myHandler.getMethod();
  27.                 //判断目标方法是否有一个@ResponseBody注解
  28.                 if (method.isAnnotationPresent(ResponseBody.class)) {
  29.                     String valueType = method.getAnnotation(ResponseBody.class).value();
  30.                     //如果注解的为默认值,或者value="json",就认为目标方法要返回的数据格式为json
  31.                     if ("json".equals(valueType) || "".equals(valueType)) {
  32.                         //对Arraylist转为json字符串
  33.                         //这里我们使用jackson包下的工具类解决
  34.                         ObjectMapper objectMapper = new ObjectMapper();
  35.                         String resultJson = objectMapper.writeValueAsString(result);
  36.                         //这里简单处理,就直接返回
  37.                         response.setContentType("text/html;charset=utf-8");
  38.                         PrintWriter writer = response.getWriter();
  39.                         writer.write(resultJson);
  40.                         writer.flush();
  41.                         writer.close();
  42.                     }
  43.                 }
  44.             }
  45.         }
  46.     } catch (Exception e) {
  47.         e.printStackTrace();
  48.     }
  49. }
复制代码
(3)pom.xml文件中引入jackson
  1. <dependency>
  2.   <groupId>com.fasterxml.jackson.core</groupId>
  3.   <artifactId>jackson-databind</artifactId>
  4.   <version>2.12.4</version>
  5. </dependency>
复制代码
(4)MonsterController 测试类增加方法测试
  1. /**
  2. * 编写方法,返回json格式的数据
  3. * 1.目标方法返回的结果是给SpringMVC底层通过反射调用的位置
  4. * 2.我们在SpringMVC底层反射调用的位置接收到结果并进行解析即可
  5. * 3. @ResponseBody(value = "json") 表示希望以json格式返回数据给浏览器
  6. * @param request
  7. * @param response
  8. * @return
  9. */
  10. @RequestMapping(value = "/monster/list/json")
  11. @ResponseBody(value = "json")
  12. public List<Monster> listMonsterByJson(HttpServletRequest request,
  13.                                        HttpServletResponse response) {
  14.     List<Monster> monsters = monsterService.listMonster();
  15.     return monsters;
  16. }
复制代码
(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 格式的数据等



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

种地

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表