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

news2024/11/26 22:51:53

手动实现SpringMVC底层机制-下

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

上一讲, 我们学习的是 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系列九: 数据格式化与验证及国际化

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

软件测试/测试开发丨Selenium如何复用已打开浏览器

步骤说明&#xff1a; 将浏览器启动方式添加到环境变量。便于我们在终端任意位置启动浏览器终端中使用命令行&#xff0c;打开浏览器debug模式代码中创建driver时&#xff0c;添加debugger_address设置 以Chrome浏览器为例&#xff0c;设置步骤如下&#xff1a; 将浏览器启动…

SpringMVC系列六: 视图和视图解析器

视图和视图解析器 &#x1f49e;基本介绍&#x1f49e; 自定义视图为什么需要自定义视图自定义试图实例-代码实现自定义视图工作流程小结Debug源码默认视图解析器执行流程多个视图解析器执行流程 &#x1f49e;目标方法直接指定转发或重定向使用实例指定请求转发流程-Debug源码…

重磅新闻!狂揽120台订单!大运重卡唐山销服一体运营店盛大开业

2024年6月13日&#xff0c;唐山市迎来了一件令人振奋的商用车行业盛事——大运重卡经销商唐山滦都汽贸暨滦州通世坤销服一体盛大开业&#xff01; 参加本次开业庆典的有大运重卡营销中心副总经理助理张申、大运重卡营销中心销售总监倪世界、唐山滦都汽车贸易有限公司总经理王力…

【STM32】使用标准库创建一个工程

创建工程 本系列文章中使用的硬件和软件版本如下&#xff1a; CPU型号&#xff1a;STM32F407ZGT6 Keil版本&#xff1a;V5.38.0.0 ARM编译器版本&#xff1a;V5.06 标准库版本&#xff1a;V1.8.0 1.下载标准库 官网链接&#xff1a;STSW-STM32065 - STM32F4 DSP和标准外…

Go语言轻松搞定Excel的操作,简单生成图表

原文链接&#xff1a;GO轻松搞定Excel 库简介 Excelize 是 Go 语言编写的用于操作 Office Excel 文档基础库&#xff0c;基于 ECMA-376&#xff0c;ISO/IEC 29500 国际标准。可以使用它来读取、写入由 Microsoft Excel™ 2007 及以上版本创建的电子表格文档。支持 XLAM / XLS…

微信小程序接入lottie动画

1、注意&#xff1a;canvas渲染出来的层级太高&#xff0c;当有弹窗的情况会暴露在弹窗外 模拟器上会有这个问题&#xff0c;线上版本不会有 2、需求 需要把lottie动画在小程序的环境下进行展示 3、什么是lottie动画 由Airbnb开发并开源。允许设计师将复杂的矢量动画导出为…

【嵌入式】SD NAND:SD卡的集成与优化

嵌入式SD卡&#xff0c;也称为SD NAND或贴片式SD卡&#xff0c;是一种专为空间受限的设备设计的存储解决方案。这种存储卡与传统的SD卡不同&#xff0c;它采用贴片式封装&#xff0c;可以直接焊接到设备的PCB上&#xff0c;从而为电子设备提供内置存储功能。以下是嵌入式SD卡的…

【论文精读】ViM: Out-Of-Distribution with Virtual-logit Matching 使用虚拟分对数匹配的分布外检测

文章目录 一、文章概览&#xff08;一&#xff09;问题来源&#xff08;二&#xff09;文章的主要工作&#xff08;三&#xff09;相关研究 二、动机&#xff1a;Logits 中缺失的信息&#xff08;一&#xff09;logits&#xff08;三&#xff09;基于零空间的 OOD 评分&#xf…

Python单行代码:一招鲜,吃遍天

大家好&#xff0c;在Python编程中&#xff0c;我们时常需要高效、简洁的代码来解决复杂的问题。今天&#xff0c;我将向大家介绍10个非常有用的Python单行代码。 一行代码指的是将复杂的任务浓缩在一行代码中完成。它充分利用Python的简洁和强大&#xff0c;使代码更简洁、更…

grafana连接influxdb2.x做数据大盘

连接influxdb 展示数据 新建仪表盘 选择存储库 设置展示

数据可视化---绘制常用图表,组合图表,定制图表主题

题目一&#xff1a;绘制桑基图&#xff0c;展示某商铺新老客服群体的商品喜好 编写程序。根据第9.3.7&#xff0c;绘制桑基图&#xff0c;展示某商铺新老客服群体的商品喜好。 运行代码&#xff1a; #绘制桑基图&#xff0c;展示某商铺新老客服群体的商品喜好 from pyecharts…

男士内裤哪个品牌质量好?国内质量好的男士内裤推荐

今天想和大家分享一个虽不起眼但至关重要的时尚单品——男士内裤。它可能不像外套或鞋子那样引人注目&#xff0c;但却承载着男士们日常的舒适与健康。选择一款合适的男士内裤&#xff0c;不仅能提升穿着体验&#xff0c;更是展现个人品味和生活态度的关键。以下是一些选择内裤…

机器学习实战19-利用机器学习模型与算法实现销售数据的归因分析与图形生成

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下机器学习实战19-利用机器学习模型与算法实现销售数据的归因分析与图形生成。 归因分析是数据分析中的一个重要环节&#xff0c;它主要用于确定不同因素对特定结果&#xff08;如销售额、转化率等&#xff09;的贡献…

Nuxt.js 深入浅出:目录结构与文件组织详解

title: Nuxt.js 深入浅出&#xff1a;目录结构与文件组织详解 date: 2024/6/18 updated: 2024/6/18 author: cmdragon excerpt: 摘要&#xff1a;本文详述了Nuxt.js框架中关键目录与配置文件的作用及使用方法&#xff0c;包括布局设定、页面结构管理、插件集成、静态资源处理…

Linux安装并配置Java

1、Linux操作系统安装Java 1.1、下载Java JDK解压包 官方提供的网址&#xff1a; 选择Linux版本 下载列表选择最后一项&#xff0c;通过解压方式安装 倒数第二项是以rpm方式进行安装&#xff0c;另一篇安装MySQL时就是用到rpm方法。 1.2、解压JDK tar -zxvf jdk-8u411-lin…

建筑幕墙甲级设计资质:申请条件与评分标准

建筑幕墙甲级设计资质的申请条件与评分标准可以清晰归纳如下&#xff1a; 申请条件 一、企业基本情况 独立企业法人资格&#xff1a;企业需具有独立企业法人资格。注册资本&#xff1a;注册资本不少于300万元人民币。 二、技术人员条件 主要技术负责人或总工程师&#xff…

1688新品爆款筛选技巧,电商小白也能秒变选品达人

不管是做淘宝、拼多多、抖音、小红书、京东等国内电商平台的商家&#xff0c;可能都有去1688选品的需求。电商环境越来越卷&#xff0c;为了迎合市场和消费者&#xff0c;商家必须不断推陈出新&#xff0c;挖掘潜力新品。 所以有的商友表示想选1688上所有带新品标识的品&#…

Hi3861 OpenHarmony嵌入式应用入门--轮询按键

本篇介绍使用轮询方式读取gpio状态来判断按键状态。 原理图如下 GPIO API API名称 说明 hi_u32 hi_gpio_init(hi_void); GPIO模块初始化 hi_u32 hi_io_set_pull(hi_io_name id, hi_io_pull val); 设置某个IO上下拉功能。 hi_u32 hi_gpio_set_dir(hi_gpio_idx id, hi_gpi…

ollama部署本地大模型

文章目录 一、Ollama1.ollama简介2.安装流程&#xff08;1&#xff09;官网下载ollama&#xff08;2&#xff09;选择特定的大模型版本下载安装&#xff08;3&#xff09;测试 二、spring项目集成Ollama的大模型1.环境信息2.配置文件&#xff08;1&#xff09;application.yml&…

shell命令(进程管理和用户管理)

一、进程处理相关命令 1、进程的概念 进程的概念主要有两点&#xff1a; 进程是一个实体。每一个进程都有它自己的地址空间&#xff0c;一般情况下&#xff0c;包括文本区域&#xff08; text region &#xff09;、数据区域&#xff08; data region &#xff09;和堆栈&am…