SpringMVC系列八: 手动实现SpringMVC底层机制-下

手动实现SpringMVC底层机制-下

  • 实现任务阶段五
    • 🍍完成Spring容器对象的自动装配-@Autowired
  • 实现任务阶段六
    • 🍍完成控制器方法获取参数-@RequestParam
      • 1.🥦将 方法的 HttpServletRequest 和 HttpServletResponse 参数封装到数组, 进行反射调用
      • 2.🥦在方法形参处, 指定 @RequestParam, 将对应的实参封装到参数数组, 进行反射调用
      • 3.🥦在方法形参 没有指定 @RequestParam, 按照默认参数名获取值, 进行反射调用
  • 实现任务阶段七
    • 🍍完成简单视图解析
  • 实现任务阶段八
    • 🍍完成返回JSON格式数据-@ResponseBody
      • 🥦分析+代码实现
      • 🥦完成测试

在本篇文章中,我们将继续深入探讨如何手动实现SpringMVC的底层机制。通过这两部分的学习,你将全面理解SpringMVC的工作原理。


⬅️ 上一讲: SpringMVC系列七: 手动实现SpringMVC底层机制-上



🔧 需要用到的项目: zzw-springmvc项目


在这里插入图片描述

实现任务阶段五

🍍完成Spring容器对象的自动装配-@Autowired

说明: 完成Spring容器中对象的注入/自动装配

示意图[分析说明]

在这里插入图片描述

分析:
加入@Autowired注解, 进行对象属性的装配. -如图
在这里插入图片描述

测试:
浏览器输入 http://localhost:8080/monster/list, 返回列表信息
在这里插入图片描述

代码实现:
1.在com.zzw.zzwspringmvc.annotation下新建@Autowired

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    String value() default "";
}

1.1MonsterController添加属性monsterService, 标注@Autowired

@Controller
public class MonsterController {

    //@Autowired表示要完成属性的装配.
    @Autowired
    private MonsterService monsterService;

	.....
}

2.ZzwWebApplicationContext增加方法executeAutowired()

//编写方法, 完成属性的自动装配
public void executeAutowired() {
    //判断ioc有没有要装配的对象
    if (ioc.isEmpty()) {
        return;//你也可以抛出异常, throw new RuntimeException("ioc 容器没有bean对象")
    }

    //遍历ioc容器中的所有注入的bean对象, 然后获取到bean的所有字段/属性, 判断是否需要装配
    /**
     * entry => <String, Object> -> String 就是你注入对象时的名称, Object就是bean对象
     */
    for (Map.Entry<String, Object> entry : ioc.entrySet()) {
        
        //String key = entry.getKey();
        Object bean = entry.getValue();
        
        //获取bean的所有字段/属性
        Field[] declaredFields = bean.getClass().getDeclaredFields();
        for (Field declaredField : declaredFields) {
            //判断当前这个字段, 是否有@Autowired
            if (declaredField.isAnnotationPresent(Autowired.class)) {//有@Autowired
                //得到当前这个字段的@Autowired
                Autowired autowiredAnnotation = declaredField.getDeclaredAnnotation(Autowired.class);
                String beanName = autowiredAnnotation.value();
                if ("".equals(beanName)) {//如果没有设置value, 按照默认规则
                    //即得到字段类型名称的首字母小写, 作为名字来进行装配
                    Class<?> type = declaredField.getType();
                    beanName = type.getSimpleName().substring(0, 1).toLowerCase()
                            + type.getSimpleName().substring(1);
                }

                //如果设置了value, 直接按照beanName来装配
                //从ioc容器中获取bean
                if (ioc.get(beanName) == null) {//说明你指定的名字对应的bean不在ioc容器
                    throw new RuntimeException("ioc容器中, 不存在你要装配的bean");
                }
                //防止属性是private, 我们需要暴力破解
                declaredField.setAccessible(true);
                try {
                    declaredField.set(bean, ioc.get(beanName));
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

3.ZzwWebApplicationContext.javainit()方法的最后添加三行代码

//编写方法, 完成自己的spring容器的初始化
public void init() {
    //这里我们写的是固定的spring容器配置文件 => 做活
    //String basePackage = XMLParser.getBasePackage("zzwspringmvc.xml");
    String basePackage =
            XMLParser.getBasePackage(configLocation.split(":")[1]);
    //这时basePackages => com.zzw.controller, com.zzw.service
    String[] basePackages = basePackage.split(",");
    if (basePackages.length > 0) {
        for (String pack : basePackages) {
            scanPackage(pack.trim());
        }
    }
    System.out.println("basePackage=" + basePackage);
    System.out.println("classFullPathList=" + classFullPathList);
    //将扫描到的类, 反射到ioc容器
    executeInstance();
    System.out.println("扫描后 ioc容器=" + ioc);

    //完成注入的bean对象,的属性的装配
    executeAutowired();
    System.out.println("装配后 ioc容器=" + ioc);

3.打断点, debug, 重启tomcat

在这里插入图片描述
在这里插入图片描述

4.修改MonsterControllerlistMonster方法

@RequestMapping(value = "/monster/list")
public void listMonster(HttpServletRequest request, HttpServletResponse response) {
    //设置返回编码和返回类型
    response.setContentType("text/html;charset=utf-8");

    StringBuilder content = new StringBuilder("<h1>妖怪列表信息</h1>");//单线程使用StringBuilder
    content.append("<table border='1px' width='500px' style='border-collapse:collapse'>");
    //调用monsterService
    List<Monster> monsters = monsterService.listMonster();
    for (Monster monster : monsters) {
        content.append("<tr><td>" + monster.getId() + "</td><td>" + monster.getName() +
                "</td><td>" + monster.getAge() + "</td><td>" + monster.getSkill() + "</td></tr>");
    }
    content.append("</table>");
    //获取writer返回信息
    try {
        response.getWriter().write(content.toString());
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

5.重启tomcat, 浏览器输入 http://localhost:8080/monster/list
在这里插入图片描述

实现任务阶段六

🍍完成控制器方法获取参数-@RequestParam

功能说明: 自定义@RequestParam 和 方法参数名获取参数
完成: 将 方法的 HttpServletRequest 和 HttpServletResponse 参数封装到数组, 进行反射调用
完成: 在方法参数 指定 @RequestParam 的参数封装到参数数组, 进行反射调用
完成: 在方法参数 没有指定 @RequestParam, 按照默认参数名获取值, 进行反射调用

示意图[分析说明]
在这里插入图片描述

1.🥦将 方法的 HttpServletRequest 和 HttpServletResponse 参数封装到数组, 进行反射调用

修改ZzwDispatcherServletexecuteDispatcher()方法

//编写方法, 完成分发请求任务
private void executeDispatcher(HttpServletRequest request,
                               HttpServletResponse response) {
    try {
        ZzwHandler zzwHandler = getZzwHandler(request);
        if (zzwHandler == null) {//说明用户请求的路径/资源不存在
            response.getWriter().print("<h1>404 NOT FOUND!</h1>");
        } else {//匹配成功, 反射调用控制器的方法

            //目标将: HttpServletRequest 和 HttpServletResponse 封装到参数数组
            //1. 得到目标方法的所有形参参数信息[返回对应的数组]
            Class<?>[] parameterTypes =
                    zzwHandler.getMethod().getParameterTypes();

            //2. 创建一个参数数组[对应实参数组], 在后面反射调用目标方法时, 会使用到
            Object[] params = new Object[parameterTypes.length];

            //3.遍历parameterTypes形参数组, 根据形参数组信息, 将实参填充到实参数组中
            for (int i = 0; i < parameterTypes.length; i++) {
                //取出每一个形参"类型"
                Class<?> parameterType = parameterTypes[i];
                //如果这个形参类型是HttpServletRequest, 将request填充到params
                //在原生的SpringMVC中, 是按照类型来进行匹配的, 老师这里简化, 使用名字来匹配
                if ("HttpServletRequest".equals(parameterType.getSimpleName())) {
                    params[i] = request;
                } else if("HttpServletResponse".equals(parameterType.getSimpleName())){
                    params[i] = response;
                }
            }

            /**
             * 1.下面这样写, 其实是针对目标方法 m(HttpServletRequest request, HttpServletResponse response)
             * zzwHandler.getMethod()
             *              .invoke(zzwHandler.getController(), request, response)
             * 2.这里我们准备将需要传递给目标方法的 实参 => 封装到参数数组
             *   => 然后以反射调用的方式传递给目标方法
             * 源码: public Object invoke(Object var1, Object... var2)...
             */
            zzwHandler.getMethod().
                    invoke(zzwHandler.getController(), params);
        }
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

2.🥦在方法形参处, 指定 @RequestParam, 将对应的实参封装到参数数组, 进行反射调用

1.MonsterService新添方法findMonsterByName

public interface MonsterService {
    //增加方法, 通过传入的name, 返回对应的monster列表
    public List<Monster> findMonsterByName(String name);
}

MonsterServiceImpl将其实现

@Service
public class MonsterServiceImpl implements MonsterService {

    public List<Monster> findMonsterByName(String name) {
        //这里我们模拟数据->DB
        List<Monster> monsters = new ArrayList<Monster>();
        monsters.add(new Monster(100, "牛魔王", "芭蕉扇", 400));
        monsters.add(new Monster(200, "汤姆猫", "抓老鼠", 200));
        monsters.add(new Monster(300, "红孩儿", "三昧真火", 100));
        monsters.add(new Monster(400, "黄袍怪", "吐烟雾", 300));
        monsters.add(new Monster(500, "白骨精", "美人计", 800));

        //创建集合并且返回查询到的monster集合
        List<Monster> findMonsters = new ArrayList<Monster>();
        //遍历monsters集合, 返回满足条件的对象
        for (Monster monster : monsters) {
            if (monster.getName().contains(name)) {
                findMonsters.add(monster);
            }
        }
        return findMonsters;
    }
}

2.com.zzw.zzwspringmvc.annotation下新建@RequestParam

/**
 * @author 赵志伟
 * @version 1.0
 * RequestParam 注解 标注在目标方法的参数上, 表示对应http请求的参数
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)//runtime表示在反射时可以拿到这个注解
@Documented
public @interface RequestParam {
    String value() default "";
}

3.ZzwDispatcherServlet增添代码

注意点
1)method.getParameters(): 得到所有的形参
2)method.getParameterTypes(): 得到形参的类型

//编写方法, 完成分发请求任务
private void executeDispatcher(HttpServletRequest request,
                               HttpServletResponse response) {
    try {
        ZzwHandler zzwHandler = getZzwHandler(request);
        if (zzwHandler == null) {//说明用户请求的路径/资源不存在
            response.getWriter().print("<h1>404 NOT FOUND!</h1>");
        } else {//匹配成功, 反射调用控制器的方法

            //目标将: HttpServletRequest 和 HttpServletResponse 封装到参数数组
            //1. 得到目标方法的所有形参参数信息[对应的数组]
            Class<?>[] parameterTypes =
                    zzwHandler.getMethod().getParameterTypes();

            //2. 创建一个参数数组[对应实参数组], 在后面反射调用目标方法时, 会使用到
            Object[] params = new Object[parameterTypes.length];

            //3.遍历parameterTypes形参数组, 根据形参数组信息, 将实参填充到实参数组中
            for (int i = 0; i < parameterTypes.length; i++) {
                //取出每一个形参类型
                Class<?> parameterType = parameterTypes[i];
                //如果这个形参是HttpServletRequest, 将request填充到params
                //在原生的SpringMVC中, 是按照类型来进行匹配的, 老师这里简化, 使用名字来匹配
                if ("HttpServletRequest".equals(parameterType.getSimpleName())) {
                    params[i] = request;
                } else if ("HttpServletResponse".equals(parameterType.getSimpleName())) {
                    params[i] = response;
                }
            }

            //将http请求参数封装到params数组中, 老韩提示, 要注意填充实参的时候, 顺序问题 👈

            //1.获取http请求的参数集合
            //老韩解读
            //2.返回的Map<String, String[]> String: 表示http请求的参数名
            //  String[]: 表示http请求的参数值, 想一下为什么是数组?
            //http://localhost:8080/monster/find?name=牛魔王&hobby=打篮球&hobby=喝酒&hobby=吃肉(防止有类似checkbox)
            Map<String, String[]> parameterMap =
                    request.getParameterMap();

            //3.遍历parameterMap, 将请求参数, 按照顺序填充到实参数组
            for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {

                //取出key. 这个name就是对应请求的参数名
                String name = entry.getKey();
                //说明: 只考虑提交的数据是单值的情况, 即不考虑类似checkbox提交的数据
                //     老师这里做了简化, 如果考虑多值情况, 也不难
                String value = entry.getValue()[0];

                //我们得到请求的参数对应我们目标方法的第几个形参, 然后将其填充
                //这里专门编写一个方法, 得到请求参数对应的是第几个形参
                //1. API 2.java内力真正增加 3.老韩忠告..
                int indexRequestParameterIndx =
                        getIndexRequestParameterIndex(zzwHandler.getMethod(), name);
                if (indexRequestParameterIndx != -1) {//找到对应的位置
                    params[indexRequestParameterIndx] = value;
                } else {//说明并没有找到@RequestParam注解对应的参数, 就会使用默认的机制进行匹配[待...]
                    //一会再写
                }
            }                                                                 👈
            /**
             * 1.下面这样写, 其实是针对目标方法 m(HttpServletRequest request, HttpServletResponse response)
             * zzwHandler.getMethod()
             *              .invoke(zzwHandler.getController(), request, response)
             * 2.这里我们准备将需要传递给目标方法的 实参=> 封装到参数数组=> 然后以反射调用的方式传递给目标方法
             * public Object invoke(Object var1, Object... var2)...
             */
            zzwHandler.getMethod().
                    invoke(zzwHandler.getController(), params);
        }
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

//编写方法, 返回请求参数是目标方法的第几个形参

/**
 * @param method 目标方法
 * @param name   请求的参数名
 * @return 是目标方法的第几个形参
 */
public int getIndexRequestParameterIndex(Method method, String name) {
    //1.得到method的所有形参参数
    Parameter[] parameters = method.getParameters();
    for (int i = 0; i < parameters.length; i++) {
        //取出当前的参数
        Parameter parameter = parameters[i];
        //判断parameter是不是有@RequestParam注解
        boolean annotationPresent = parameter.isAnnotationPresent(RequestParam.class);
        if (annotationPresent) {//说明有@RequestParam
            //取出当前这个参数的@RequestParam(value = "xxx")
            RequestParam requestParamAnnotation =
                    parameter.getAnnotation(RequestParam.class);
            String value = requestParamAnnotation.value();
            //这里就是匹配的比较
            if (name.equals(value)) {
                return i;//找到请求的参数, 对应的目标方法的形参的位置
            }
        }
    }
    //如果没有匹配成功, 就返回-1
    return -1;
}

4.MonsterController增加如下方法

//增加方法, 通过name返回对应的monster对象
@RequestMapping(value = "/monster/find")
public void findMonsterByName(HttpServletRequest request,
                              HttpServletResponse response,
                              @RequestParam(value = "name") String name) {
    //设置返回编码和返回类型
    response.setContentType("text/html;charset=utf8");
    System.out.println("---接收到的name---" + name);👈
    StringBuilder content = new StringBuilder("<h1>妖怪列表信息</h1>");//单线程使用StringBuilder
    content.append("<table border='1px' width='500px' style='border-collapse:collapse'>");
    //调用monsterService
    List<Monster> monsters = monsterService.findMonsterByName(name);👈
    for (Monster monster : monsters) {
        content.append("<tr><td>" + monster.getId() + "</td><td>" + monster.getName() + "</td><td>"
                + monster.getAge() + "</td><td>" + monster.getSkill() + "</td></tr>");
    }
    content.append("</table>");

    //获取writer返回信息
    try {
        response.getWriter().write(content.toString());
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

5.测试
在这里插入图片描述在这里插入图片描述

3.🥦在方法形参 没有指定 @RequestParam, 按照默认参数名获取值, 进行反射调用

1.去掉MonsterControllerfindMonsterByName()方法的name字段的@RequestParam注解

//增加方法, 通过name返回对应的monster对象
@RequestMapping(value = "/monster/find")
public void findMonsterByName(HttpServletRequest request,
                              HttpServletResponse response,
                              String name) {
        .....
}

2.ZzwDispatcherServletexecuteDispatcher()else分支内增加如下代码, 并增加方法getParameterNames

//编写方法, 完成分发请求任务
private void executeDispatcher(HttpServletRequest request,
                               HttpServletResponse response) {
    try {
        ZzwHandler zzwHandler = getZzwHandler(request);
        if (zzwHandler == null) {//说明用户请求的路径/资源不存在
            response.getWriter().print("<h1>404 NOT FOUND!</h1>");
        } else {//匹配成功, 反射调用控制器的方法

            //目标将: HttpServletRequest 和 HttpServletResponse 封装到参数数组
            //1. 得到目标方法的所有形参参数信息[对应的数组]
            Class<?>[] parameterTypes =
                    zzwHandler.getMethod().getParameterTypes();

            //2. 创建一个参数数组[对应实参数组], 在后面反射调用目标方法时, 会使用到
            Object[] params = new Object[parameterTypes.length];

            //3.遍历parameterTypes形参数组, 根据形参数组信息, 将实参填充到实参数组中
            for (int i = 0; i < parameterTypes.length; i++) {
                //取出每一个形参类型
                Class<?> parameterType = parameterTypes[i];
                //如果这个形参是HttpServletRequest, 将request填充到params
                //在原生的SpringMVC中, 是按照类型来进行匹配的, 老师这里简化, 使用名字来匹配
                if ("HttpServletRequest".equals(parameterType.getSimpleName())) {
                    params[i] = request;
                } else if ("HttpServletResponse".equals(parameterType.getSimpleName())) {
                    params[i] = response;
                }
            }

            //将http请求参数封装到params数组中, 老韩提示, 要注意填充实参的时候, 顺序问题

            //1.获取http请求的参数集合
            //老韩解读
            //http://localhost:8080/monster/find?name=牛魔王&hobby=打篮球&hobby=喝酒&hobby=吃肉(防止有类似checkbox)
            //2.返回的Map<String, String[]> String: 表示http请求的参数名
            //  String[]: 表示http请求的参数值, 为什么是数组
            //
            Map<String, String[]> parameterMap = request.getParameterMap();

            //2.遍历parameterMap, 将请求参数, 按照顺序填充到实参数组
            for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {

                //取出key. 这个name就是对应请求的参数名
                String name = entry.getKey();
                //说明: 只考虑提交的数据是单值的情况, 即不考虑类似checkbox提交的数据
                //     老师这里做了简化, 如果考虑多值情况, 也不难
                String value = entry.getValue()[0];

                //我们得到请求的参数对应我们目标方法的第几个形参, 然后将其填充
                //这里专门编写一个方法, 得到请求参数对应的是第几个形参
                //1. API 2.java内力真正增加 3.老韩忠告..
                int indexRequestParameterIndx =
                        getIndexRequestParameterIndex(zzwHandler.getMethod(), name);
                if (indexRequestParameterIndx != -1) {//找到对应的位置
                    params[indexRequestParameterIndx] = value;
👉👉👉👉👉👉👉} else {//说明并没有找到@RequestParam注解对应的参数, 就会使用默认的机制进行配置[待...]
                    //思路 														
                    //1.得到目标方法的所有形参的名称, 而不是形参类型的名称-专门编写一个方法获取形参名
                    //2.对得到的目标方法的所有形参名进行遍历, 如果匹配就把当前请求的参数值, 填充到params
                    List<String> parameterNames = getParameterNames(zzwHandler.getMethod());
                    for (int i = 0; i < parameterNames.size(); i++) {
                        //如果请求参数名和目标方法的形参名一样, 说明匹配成功
                        if (name.equals(parameterNames.get(i))) {
                            params[i] = value;//填充到实参数组
                            break;
                        }
                    }                  									
                }
            }
            /**
             * 1.下面这样写, 其实是针对目标方法 m(HttpServletRequest request, HttpServletResponse response)
             * zzwHandler.getMethod()
             *              .invoke(zzwHandler.getController(), request, response)
             * 2.这里我们准备将需要传递给目标方法的 实参=> 封装到参数数组=> 然后以反射调用的方式传递给目标方法
             * public Object invoke(Object var1, Object... var2)...
             */
            zzwHandler.getMethod().
                    invoke(zzwHandler.getController(), params);
        }
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

//编写方法, 得到目标方法的所有形参的名称, 并放入到集合中返回

/**
 * @param method 目标方法
 * @return 所有形参的名称, 并放入到集合中返回
 */
public List<String> getParameterNames(Method method) {
    List<String> parametersList = new ArrayList<String>();
    //获取到所有的参数名称, 而不是参数类型的名称
    //这里有一个小细节-->在默认情况下 parameter.getName()
    //得到的名字不是形参真正的名字, 而是 [arg0, arg1, arg2...]
    //, 这里我们要引入一个插件, 使用java8的特性, 这样才能解决
    Parameter[] parameters = method.getParameters();
    //遍历parameters, 取出名字, 放入parametersList
    for (Parameter parameter : parameters) {
        parametersList.add(parameter.getName());
    }
    System.out.println("目标方法的形参参数列表=" + parametersList);
    return parametersList;
}

3.parameter.getName()得到的名字不是形参真正的名字, 而是 [arg0, arg1, arg2…].
Parameter[] parameters = method.getParameters();
parameters=[javax.servlet.http.HttpServletRequest arg0,
                     javax.servlet.http.HttpServletResponse arg1,
                     java.lang.String arg2]

parameter.getName() ⇒ [arg0, arg1, arg2]

我们需要引入一个插件.
pom.xmlbuild节点内插入以下代码 -parameters, 点击右上角刷新

<build>
  <finalName>zzw-springmvc2(你的文件名)</finalName>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.7.0</version>
      <configuration>
        <source>1.8</source>
        <target>1.8</target>
        <compilerArgs>
          <arg>-parameters</arg>
        </compilerArgs>
        <encoding>utf-8</encoding>
      </configuration>
    </plugin>
  </plugins>
</build>

3.1点击maven管理, Lifecycle目录下, clean项目, 重启(不是重新部署)tomcat.
在这里插入图片描述

5.测试
在这里插入图片描述在这里插入图片描述

实现任务阶段七

🍍完成简单视图解析

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

●完成任务说明
-用户输入白骨精, 可以登陆成功, 否则失败
-根据登陆的结果, 可以重定向或者请求转发到 login_ok.jsp / login_error.jsp, 并显示妖怪名

-思路分析示意图
在这里插入图片描述

1.在webapp目录下新建

1)login.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登陆页面</title>
</head>
<body>
<h1>登陆页面</h1>
<form action="?" method="post">
    妖怪名: <input type="text" name="mName"/><br/>
    <input type="submit" value="登录">
</form>
</body>
</html>

2)login_ok.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登录成功</title>
</head>
<body>
<h1>登陆成功</h1>
欢迎你: ${?}
</body>
</html>

3)login_error.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登录失败</title>
</head>
<body>
<h1>登陆失败</h1>
sorry, 登陆失败 ${?}
</body>
</html>

2.MonsterService增加一个方法login

public interface MonsterService {
	....
    //增加方法, 处理登录
    public boolean login(String name);
}

MonsterServiceImpl实现它

@Service
public class MonsterServiceImpl implements MonsterService {
	....
    @Override
    public boolean login(String name) {
        //实际上会到DB验证->这里我们模拟一下
        if ("白骨精".equals(name)) {
            return true;
        } else {
            return false;
        }
    }
}

3.MonsterController添加一个方法login

//处理妖怪登陆的方法, 返回要请求转发/重定向的字符串
@RequestMapping("/monster/login")
public String login(HttpServletRequest request, HttpServletResponse response, String mName) {
    System.out.println("---接收到mName---" + mName);
    boolean b = monsterService.login(mName);
    if (b) {//登陆成功
        return "forward:/login_ok.jsp";
    } else {//登陆失败
        return "forward:/login_error.jsp";
    }
}

接着 login.jsp填充action="/monster/login"

<h1>登陆页面</h1>
<%--第一个/会被解析 http://localhost:8080
/monster/login => http://localhost:8080/monster/login--%>
<form action="/monster/login" method="post">
    妖怪名: <input type="text" name="mName"/><br/>
    <input type="submit" value="登录">
</form>

4.测试
在这里插入图片描述在这里插入图片描述

如果输入中文, 发现提交的数据有中文乱码问题, 因为是post请求.
在这里插入图片描述
在这里插入图片描述


解决方案
我们在底层解决乱码问题.
ZzwDispatcherServlet前端控制器完成分发请求任务executeDispatcher()方法内, 添加如下代码 request.setCharacterEncoding("utf-8");即可

//2.返回的Map<String, String[]> String: 表示http请求的参数名
//  String[]: 表示http请求的参数值, 为什么是数组
//处理提交的数据中文乱码问题
request.setCharacterEncoding("utf-8");
Map<String, String[]> parameterMap = request.getParameterMap();

测试
在这里插入图片描述


5.在ZzwDispatcherServletexecuteDispatcher方法, 添加如下代码

//上面代码省略...

/**
 * 1.下面这样写, 其实是针对目标方法 m(HttpServletRequest request, HttpServletResponse response)
 * zzwHandler.getMethod()
 *              .invoke(zzwHandler.getController(), request, response)
 * 2.这里我们准备将需要传递给目标方法的 实参=> 封装到参数数组=> 然后以反射调用的方式传递给目标方法
 * public Object invoke(Object var1, Object... var2)...
 */

//反射调用目标方法
Object result = zzwHandler.getMethod()
        .invoke(zzwHandler.getController(), params);

//这里就是对返回的结果进行解析=>原生springmvc 可以通过视图解析器来完成
//这里老师让我们直接解析, 只要把视图解析器的核心机制表达清楚就OK
//instanceof 判断 运行类型
if (result instanceof String) {
    String viewName = (String) result;
    if (viewName.contains(":")) {//说明你返回的String 结果forward:/login_ok.jsp 或者 redirect:/xxx/xx/xx.xx
        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)) {//说明你希望重定向
            //如果是redirect, 那么这里需要拼接Application  Context. 只不过这个项目的Application Context 正好是 /
            response.sendRedirect(viewPage);
        }
    } else {//默认是请求转发
        request.getRequestDispatcher(viewName)
                .forward(request, response);
    }
}//这里还可以扩展

6.测试
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

解决{?}:将信息保存到request域, 在页面显示.
6.MonsterController修改login方法, 将mName保存到request域

//处理妖怪登陆的方法, 返回要请求转发/重定向的字符串
@RequestMapping("/monster/login")
public String login(HttpServletRequest request, HttpServletResponse response, String mName) {
    System.out.println("---接收到mName---" + mName);
    //将nName设置到request域
    request.setAttribute("mName", mName);
    boolean b = monsterService.login(mName);
    if (b) {//登陆成功
        return "forward:/login_ok.jsp";
    } else {//登陆失败
        return "forward:/login_error.jsp";
    }
}

6.1修改login_ok.jsp. 设置isELIgnored="false", 否则el表达式不会生效, 默认是true

<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
    <title>登录成功</title>
</head>
<body>
<h1>登陆成功</h1>
欢迎你: ${requestScope.mName}
</body>
</html>

6.2修改login_error.jsp.设置isELIgnored="false", 否则el表达式不会生效 默认是true

<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
    <title>登录失败</title>
</head>
<body>
<h1>登陆失败</h1>
sorry, 登陆失败: ${requestScope.mName}
</body>
</html>

6.3测试
在这里插入图片描述在这里插入图片描述

7.演示redirect.
MonsterController修改login方法

//处理妖怪登陆的方法, 返回要请求转发/重定向的字符串
@RequestMapping("/monster/login")
public String login(HttpServletRequest request, HttpServletResponse response, String mName) {
    System.out.println("---接收到mName---" + mName);
    //将nName设置到request域
    request.setAttribute("mName", mName);
    boolean b = monsterService.login(mName);
    if (b) {//登陆成功
        //return "forward:/login_ok.jsp";
        //测试从定向
        return "redirect:/login_ok.jsp";👈
    } else {//登陆失败
        return "forward:/login_error.jsp";
    }
}

7.1测试

在这里插入图片描述之所有不显示, 是因为请求方式是重定向.
在这里插入图片描述
在这里插入图片描述

7.2演示默认方式(forward)
MonsterController修改login方法

//处理妖怪登陆的方法, 返回要请求转发/重定向的字符串
@RequestMapping("/monster/login")
public String login(HttpServletRequest request, HttpServletResponse response, String mName) {
    System.out.println("---接收到mName---" + mName);
    //将nName设置到request域
    request.setAttribute("mName", mName);
    boolean b = monsterService.login(mName);
    if (b) {//登陆成功
        //return "forward:/login_ok.jsp";
        //测试从定向
        //return "redirect:/login_ok.jsp";
        //测试默认方式(forward)
        return "/login_ok.jsp";👈
    } else {//登陆失败
        return "forward:/login_error.jsp";
    }
}

8 将登录页面提交的参数名改成monsterName, 还会不会登陆成功?

<h1>登陆页面</h1>
<%--第一个/会被解析 http://localhost:8080
/monster/login => http://localhost:8080/monster/login--%>
<form action="/monster/login" method="post">
    妖怪名: <input type="text" name="monsterName"/><br/>
    <input type="submit" value="登录">
</form>

8.1测试
在这里插入图片描述在这里插入图片描述

8.2 解决方案: 在MonsterControllerlogin方法, 形参mName加上@RequestParam(value = "monsterName")

//处理妖怪登陆的方法, 返回要请求转发/重定向的字符串
@RequestMapping("/monster/login")
public String login(HttpServletRequest request,
                    HttpServletResponse response,
                    @RequestParam(value = "monsterName") String mName) {
    System.out.println("---接收到mName---" + mName);
    //将nName设置到request域
    request.setAttribute("mName", mName);
    boolean b = monsterService.login(mName);
    if (b) {//登陆成功
        //return "forward:/login_ok.jsp";
        //测试从定向
        //return "redirect:/login_ok.jsp";
        //测试默认方式-forward
        return "/login_ok.jsp";
    } else {//登陆失败
        return "forward:/login_error.jsp";
    }
}

8.3测试
在这里插入图片描述在这里插入图片描述

实现任务阶段八

🍍完成返回JSON格式数据-@ResponseBody

功能说明: 通过自定义@ResponseBody, 返回JSON格式数据

●完成任务说明
-在实际开发中, 比如前后端分离的项目, 通常是直接返回json数据给客户端/浏览器
-客户端/浏览器接收到数据后, 自己决定如何处理和显示
-测试页面 浏览器输入: http://localhost:8080/monster/list/json

🥦分析+代码实现

在这里插入图片描述

1.在com.zzw.zzwspringmvc.annotation下新建@ResponseBody

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody {
    String value() default "";
}

2.MonsterController添加方法listMonsterByJson

@Controller
public class MonsterController {

    //@Autowired表示要完成属性的装配.
    @Autowired
    private MonsterService monsterService;

    /**
     * 编写方法, 返回json格式的数据
     * 老师梳理
     * 1.目标方法返回的结果是给springmvc底层反射调用的位置使用
     * 2.我们在springmvc底层反射调用的位置, 接收到结果并解析即可
     * 3.方法上标注了@ResponseBody 表示希望以json格式返回给客户端/浏览器
     * 4.目标方法的实参, 在springmvc底层通过封装好的参数数组, 传入...
     * @param request
     * @param respons
     * @return
     */
    @RequestMapping("/monster/list/json")
    @ResponseBody
    public List<Monster> listMonsterByJson(HttpServletRequest request,
                                                HttpServletResponse respons) {
        List<Monster> monsters = monsterService.listMonster();
        return monsters;
    }
}

3.pom.xml引入jackson, 刷新. 注意: 这里我们不使用gson
注意: jackson-databind

<!--引入jackson, 使用它的工具类可以进行json操作-->
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.12.4</version>
</dependency>

3.1com.zzw包下新建一个测试类ZzwTest

public class ZzwTest {
    public static void main(String[] args) {
        List<Monster> monsters = new ArrayList<Monster>();
        monsters.add(new Monster(100, "牛魔王", "芭蕉扇", 400));
        monsters.add(new Monster(200, "汤姆猫", "抓老鼠", 200));

        //把monsters 转成json
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            String monsterJson = objectMapper.writeValueAsString(monsters);
            System.out.println("monsterJson=" + monsterJson);

        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }
}

4.ZzwDispatcherServletexecuteDispatcher, 在反射调用目标方法的位置扩展代码

if (result instanceof String) {
    String viewName = (String) result;
    ......
}//这里还可以扩展
else if (result instanceof ArrayList) {//如果是ArrayList
    //判断目标方法是否有@ResponseBody注解
    Method method = zzwHandler.getMethod();
    if (method.isAnnotationPresent(ResponseBody.class)) {
        //把result [ArrayList] 转成json格式数据->返回
        //这里我们需要使用到java中如何将 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();
    }
}

🥦完成测试

1.浏览器测试
在这里插入图片描述
2.也可以在线转成规整的json格式
JSON工具在线解析
在这里插入图片描述

3.也可以使用Postman进行测试
在这里插入图片描述


🔜 下一篇预告 🔜
敬请期待:SpringMVC系列九: 数据格式化与验证及国际化


📚 目录导航 📚

  1. SpringMVC系列一: 初识SpringMVC
  2. SpringMVC系列二: 请求方式介绍
  3. SpringMVC系列三: Postman(接口测试工具)
  4. SpringMVC系列四: Rest-优雅的url请求风格
  5. SpringMVC系列五: SpringMVC映射请求数据
  6. SpringMVC系列六: 视图和视图解析器
  7. SpringMVC系列七: 手动实现SpringMVC底层机制-上
  8. SpringMVC系列八: 手动实现SpringMVC底层机制-下

💬 读者互动 💬
在学习SpringMVC底层机制的过程中,你有哪些疑问或需要帮助的地方?欢迎在评论区留言,我们一起讨论。


在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/725073.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python运算符和表达式实战

1.判断回文数 回文数就是将其反向排列&#xff0c;与原来相等 n1 n2 int(input("请输入&#xff1a; ")) t 0 while n2>0 :# 取余数t t*10n2%10# 取整数n2 // 10 if n1 t:print("是回文数") else:print("不是回文数") 2.字符串转换&…

linux中“PXE高效批量装机”

在大规模的 Linux 应用环境中&#xff0c;如 Web 群集、分布式计算等&#xff0c;服务器往往并不配备光驱设备&#xff0c;在这种情况下&#xff0c;如何为数十乃至上百台服务器裸机快速安装系统呢&#xff1f;传统的 USB光驱、移动硬盘等安装方法显然已经难以满足需求。 PXE …

实现跑马灯

目录 一 设计原型 二 后台源码 一 设计原型 二 后台源码 namespace 跑马灯 {public partial class Form1 : Form{public Form1(){InitializeComponent();}private void Form1_Load(object sender, EventArgs e){Color[] colors { Color.Red, Color.Green, Color.Yellow };T…

Java集合框架源码分析:LinkedList

文章目录 一、LinkedList特性二、LinkedList底层数据结构三、LinkedList继承关系参考&#xff1a; 一、LinkedList特性 特性描述是否允许为空允许是否允许重复数据允许是否有序有序是否线程安全非线程安全 二、LinkedList底层数据结构 LinkedList同时实现了List接口和Deque接…

【紫光同创盘古PGX-Nano教程】——(盘古PGX-Nano开发板/PG2L50H_MBG324第十一章)模拟波形实验例程说明

本原创教程由深圳市小眼睛科技有限公司创作&#xff0c;版权归本公司所有&#xff0c;如需转载&#xff0c;需授权并注明出处&#xff08;www.meyesemi.com) 适用于板卡型号&#xff1a; 紫光同创PG2L50H_MBG324开发平台&#xff08;盘古PGX-Nano&#xff09; 一&#xff1a;…

哈希的基本原理

目录 一.哈希概念 二.哈希冲突 三.哈希函数 四.哈希冲突解决 一.闭散列(开放寻址法) ①插入&#xff1a; ②查找&#xff1a; ③删除&#xff1a; 代码测试&#xff1a; 二.开散列(拉链法) ①插入&#xff1a; ②查找&#xff1a; ③删除&#xff1a; 代码测试&a…

推荐一个Python的前端框架Streamlit

WHY&#xff0c;为什么要用Streamlit 你是不是也想写一个简单的前端界面做些简单的展示和控制&#xff0c;不想写html、css、js&#xff0c;也用不到前后端分离&#xff0c;用不到特别复杂的Flask、Django等&#xff0c;如果你遇到类似这样的问题&#xff0c;我推荐你试试Stre…

LSM-Tree数据结构原理

LSM-Tree树原理 什么是LSM-Tree LSM-Tree 即 Log Structrued Merge Tree&#xff0c;这是一种分层有序&#xff0c;硬盘友好的数据结构。核心思想是利用磁盘顺序写性能远高于随机写。 LSM-Tree 并不是一种严格的树结构&#xff0c;而是一种内存磁盘的多层存储结构。HBase、L…

c++中string的用法

STL的简介 一.什么是STL二.STL的六大组件2.1仿函数2.2空间配置器2.3 算法2.4 迭代器2.5容器2.6配置器 三.string类3.1string类3.2string类的常用接口说明代码示例运行结果 3.3string类对象的容量操作代码示例sizelengthcapcityempty resizereverse 3.4string类对象的访问及遍历…

LVGL开发教程-按钮Button

系列文章目录 知不足而奋进 望远山而前行 目录 系列文章目录 文章目录 前言 1. 普通Button 2.可选中Button 3.按钮事件处理 总结 前言 在图形用户界面&#xff08;GUI&#xff09;开发中&#xff0c;按钮&#xff08;Button&#xff09;是用户与程序交互的重要组件之一…

目标检测数据集 - PCB板表面缺陷检测数据集下载「包含VOC、COCO、YOLO三种格式」

数据集介绍&#xff1a;PCB 板表面缺陷检测数据集&#xff0c;真实采集高质量 PCB 板表面含缺陷图片数据&#xff0c;数据集含多款不同 PCB 板高清表面图片数据&#xff0c;包括俯拍正拍、旋转拍摄姿态。数据标注标签包括 missing_hole、mouse_bite、open_circuit、short、spur…

6.17继承

面向对象的特征&#xff1a;封装&#xff0c;继承&#xff0c;多态 使用背景&#xff1a;比如说在动物类底下可以有带毛的动物&#xff0c;带毛的动物符合所有的动物的特征&#xff0c;只是在这个基础上再继续添加一些特征 命名&#xff1a;原有类型称为“基类”或“父类”&a…

Springboot集成Mybatisplus过程

这里写目录标题 背景步骤明确标准实操过程创建好数据库&#xff0c;命名好&#xff08;这里会考察一个命名规范&#xff09;&#xff0c;表的命名&#xff0c;中间使用下划线隔离开。使用idea创建Springboot项目&#xff08;注意版本问题&#xff09;使用插件生成代码常用代码p…

Nuxt3 实战 (十):使用 Supabase 实现 RESTful 风格 API 接口

前言 本篇文章我们来使用 Supabase 实现 RESTful 风格的 API 接口&#xff0c;以此来实现网站分类和子站点的 CURD 功能。 表设计 这里需要用到两张表&#xff1a; ds_categorys&#xff1a;存储网站分类 列名类型备注iduuid主键&#xff0c;分类 idnametext分类名称desct…

重生之 SpringBoot3 入门保姆级学习(22、场景整合 Swagger 接口文档)

重生之 SpringBoot3 入门保姆级学习&#xff08;22、场景整合 Swagger 接口文档&#xff09; 6.2 Swagger 接口文档 6.2 Swagger 接口文档 1、将 starter 导入 Maven 官网 https://springdoc.org/<dependency><groupId>org.springdoc</groupId><artifact…

学习记录之数学表达式(5)

文章目录 十、线性回归10.1 示例10.2 拟合10.3 推导10.4 岭回归10.5 作业 十一、Logistic回归11.1 分割超平面11.2 点到直线的距离11.3 sigmoid函数11.4 优化目标11.5 求解11.6 作业 十、线性回归 线性回归是一个常用的机器学习算法&#xff1b; 10.1 示例 表 1.单变量的股价预…

格雷母线技术革新:推动斗轮堆取料机进入精准操作时代

随着工业4.0时代的到来&#xff0c;智能化、自动化已成为工业发展的必然趋势。特别是在港口、电力、冶金等行业中&#xff0c;散料装卸机械的智能化水平直接关系到整个生产流程的效率与安全。斗轮堆取料机作为这些行业中的关键设备&#xff0c;其操作方式的革新显得尤为重要。 …

Unity OpenCVForUnity 安装和第二个案例详解 <二>

目录 一、前言 二、场景介绍 1.WebCamTextureToMatExample脚本 2.FpsMonitor脚本 三、 结构体Scaler 四、找到相机并使用 1.相机的启用 2.格式转换 a.把webCamTexture转换成Mat b.把Mat转换成Texture2D 五、脚本组合 六、作者的碎碎念 一、前言 第二个案例&#xf…

leetcode (top100)盛最多水的容器

题目&#xff1a; 题解&#xff1a; 第一种可行的方案&#xff1a; 设置左指针指向第一条线&#xff0c;设置右指针指向最后一条线。每次向中间移动两条线中最短的一条&#xff0c;计算移动过程中最大接水量。 本题可以看出影响接水量的有两个因素&#xff0c;两条线的距离&…

空间复杂度的相关概念

1. 空间复杂度 空间复杂度&#xff08;space complexity&#xff09;用于衡量算法占用内存空间随着数据量变大时的增长趋势。 统计哪些空间&#xff1a; ● 暂存数据&#xff1a;用于保存算法运行过程中的各种常量、变量、对象等。 ● 栈帧空间&#xff1a;用于保存调用函数…