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表示要完成属性的装配.@Autowiredprivate 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) {//判断当前这个字段, 是否有@Autowiredif (declaredField.isAnnotationPresent(Autowired.class)) {//有@Autowired//得到当前这个字段的@AutowiredAutowired 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容器中获取beanif (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.serviceString[] 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>");//单线程使用StringBuildercontent.append("<table border='1px' width='500px' style='border-collapse:collapse'>");//调用monsterServiceList<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) {//这里我们模拟数据->DBList<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;//找到请求的参数, 对应的目标方法的形参的位置}}}//如果没有匹配成功, 就返回-1return -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>");//单线程使用StringBuildercontent.append("<table border='1px' width='500px' style='border-collapse:collapse'>");//调用monsterServiceList<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.对得到的目标方法的所有形参名进行遍历, 如果匹配就把当前请求的参数值, 填充到paramsList<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, 取出名字, 放入parametersListfor (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 {....@Overridepublic 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.xxString viewType = viewName.split(":")[0];//forward | redirectString viewPage = viewName.split(":")[1];//表示你要跳转的页面//判断是forward 还是 redirectif ("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";//测试默认方式-forwardreturn "/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表示要完成属性的装配.@Autowiredprivate MonsterService monsterService;/*** 编写方法, 返回json格式的数据* 老师梳理* 1.目标方法返回的结果是给springmvc底层反射调用的位置使用* 2.我们在springmvc底层反射调用的位置, 接收到结果并解析即可* 3.方法上标注了@ResponseBody 表示希望以json格式返回给客户端/浏览器* 4.目标方法的实参, 在springmvc底层通过封装好的参数数组, 传入...* @param request* @param respons* @return*/@RequestMapping("/monster/list/json")@ResponseBodypublic 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 转成jsonObjectMapper 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底层机制的过程中,你有哪些疑问或需要帮助的地方?欢迎在评论区留言,我们一起讨论。


在这里插入图片描述

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

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

相关文章

计算机跨考现状,两极分化现象很严重

其实我觉得跨考计算机对于一些本科学过高数的同学来说有天然的优势 只要高数能学会&#xff0c;那计算机那几本专业课&#xff0c;也能很轻松的拿下&#xff0c;而对于本科是文科类的专业&#xff0c;如果想跨考计算机&#xff0c;难度就不是一般的大了。 现在跨考计算机呈现…

2024北京智源大会开幕,智源推出大模型全家桶及全栈开源技术基座新版图,大模型先锋集结共探AGI之路

2024年6月14日&#xff0c;第六届“北京智源大会”在中关村展示中心开幕。 北京智源大会是智源研究院主办的“AI内行顶级盛会”&#xff0c;以“全球视野、思想碰撞、前沿引领”为特色&#xff0c;汇聚海内外研究者分享研究成果、探寻前沿知识、交流实践经验。2024北京智源大会…

【学习Docker】

学习Docker可以分为几个步骤和阶段&#xff0c;以下是一个建议的学习路径&#xff0c;适合初学者到进阶用户&#xff1a; ### 1. 理解基本概念 - **容器化与虚拟化**&#xff1a;了解容器化与传统虚拟化之间的区别&#xff0c;容器的轻量级和效率。 - **Docker组件**&#xff…

springboot优雅shutdown时如何保障异步线程的安全

我前面写了一篇springboot优雅shutdown的文章&#xff0c;看起来一切很美好。 https://blog.csdn.net/chenshm/article/details/139640775 那是因为没有进行多线程测试。如果一个请求中包括阻塞线程&#xff08;主线程&#xff09;和非阻塞线程&#xff08;异步线程&#xff09…

Linux安装MySQL以及远程连接

1、Linux安装MySQL 1.1、准备解压包 MySQL5.x解压包 提取码&#xff1a;9y7n 1.2、通过rpm脚本安装 切记安装顺序&#xff1a;common --> libs --> client --> server 因为它们之间存在依赖关系&#xff0c;所以务必按照顺序安装 安装前请确保当前目录/文…

【差分数组】2772. 使数组中的所有元素都等于零

本文涉及知识点 差分数组 LeetCode2772. 使数组中的所有元素都等于零 给你一个下标从 0 开始的整数数组 nums 和一个正整数 k 。 你可以对数组执行下述操作 任意次 &#xff1a; 从数组中选出长度为 k 的 任一 子数组&#xff0c;并将子数组中每个元素都 减去 1 。 如果你可…

【Android】安Android Studio环境搭建注意点

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…

PowerBi 获取指定时间间隔的日期的方法

获取指定时间间隔的日期&#xff0c;比如我们想得到2024年5月31日后的第三天。 网络上的教程一般是使用DATEADD()函数。 但是这个函数返回的是表。假如我们的需求是不做汇总等计算&#xff0c;只是把它作为一个计算列&#xff0c;或者度量值&#xff0c;那么我更推荐用DATE(&…

信息系统项目管理师 | 新一代信息技术

关注WX&#xff1a;CodingTechWork 物联网 定义 The Internet of Things是指通过信息传感设备&#xff0c;按约定的协议&#xff0c;将任何物品与互联网连接&#xff0c;进行信息交互和通信&#xff0c;以实现智能化识别。定位、跟踪、监控和管理的一种网络。物联网主要解决…

Part 4.4 树形动态规划

树形动态规划&#xff0c;即在树上进行的动态规划。 因为树的递归性质&#xff0c;树形动态规划一般都是递归求解的。 没有上司的舞会 题目描述 某大学有 n n n 个职员&#xff0c;编号为 1 … n 1\ldots n 1…n。 他们之间有从属关系&#xff0c;也就是说他们的关系就像…

禅道系统忘记密码-直接更改数据库数据解决

禅道系统很久不用密码忘记了&#xff0c;这里采用直接进数据库修改密码方式解决。 登录到系统&#xff0c;进入禅道安装目录&#xff0c;这里安装在/opt/zbox 基本思路如下&#xff1a; 1.找到数据库账号和密码。 2.使用mysql命令登陆数据库。 3.在禅道数据库对应用户表内更…

鸿蒙原生应用元服务开发-位置服务申请权限

申请位置权限开发指导 场景概述 应用在使用位置服务系统能力前&#xff0c;需要检查是否已经获取用户授权访问设备位置信息。如未获得授权&#xff0c;可以向用户申请需要的位置权限。 系统提供的定位权限有&#xff1a; ohos.permission.LOCATION&#xff1a;用于获取精准位置…

keepalived服务详解与实验 基于centos8

目录 keepalivedHA简介常用的高可用软件keepalived简介 keepalived常用模块keepalived功能简介keepalived常用文件keepalived配置文件详解keepalived实验1-上手环境准备安装服务主配置文件修改启动服务效果查看 keepalived脑裂1. 脑裂现象简介2. 脑裂的原因3. 脑裂的预防和解决…

电商API接口是什么意思?有什么作用?

电商API接口是电子商务领域中一种技术解决方案&#xff0c;它允许不同的软件系统之间进行交互和数据交换。 在电商场景下&#xff0c;电商API接口可以实现的功能非常丰富&#xff0c;例如&#xff1a; 商品管理&#xff1a;获取商品列表、商品详情、搜索商品、上下架商品等&a…

一文详解:什么是小程序SDK?

什么是小程序SDK&#xff1f; 首先来看看概念&#xff1a;小程序SDK&#xff08;Software Development Kit&#xff09;是用于开发和扩展小程序的工具集合。可以理解为一套工具箱&#xff0c;专门帮助开发者建立和定制小程序应用程序。这些工具包括了开发小程序所需的各种代码…

【网络编程】多进程服务器端

并发服务器的实现 多进程服务器:通过创建多个进程提供服务多路复用服务器:通过捆绑并统一管理IO对象提供服务。多线程服务器:通过生成与客户端等量的线程提供服务。、 理解进程process 定义&#xff1a;占用内存空间的正在运行的程序。 CPU核和进程数&#xff1a;1个CPU 中…

wegame启动游戏错误代码126,加载x3daudio1_7.dll失败怎么解决

x3daudio1_7.dll是一个重要的动态链接库文件&#xff0c;属于Microsoft DirectX SDK的一部分&#xff0c;主要服务于音频处理领域&#xff0c;特别是在游戏和多媒体应用程序中提供高级的3D音频效果。 基本属性与功能 文件名称&#xff1a;x3daudio1_7.dll 类型&#xff1a;动…

YOLO-World:开启实时开放词汇目标检测的新篇章

目标检测作为计算机视觉领域的基石之一&#xff0c;其发展一直备受学术界和工业界的关注。传统的目标检测方法通常受限于固定词汇表的约束&#xff0c;即只能在预定义的类别集合中进行检测。然而&#xff0c;现实世界中的对象种类繁多&#xff0c;远远超出了任何固定词汇表的覆…

MySQl基础入门⑯【操作视图】完结

上一边文章内容 表准备 CREATE TABLE Students (id INT AUTO_INCREMENT PRIMARY KEY,name VARCHAR(100),email VARCHAR(255),major VARCHAR(100),score int,phone_number VARCHAR(20),entry_year INT,salary DECIMAL(10, 2) );数据准备 INSERT INTO Students (id, name, ema…

windows修改hosts文件、windows刷新dns缓存

文章目录 一、windows修改hosts文件 一、windows修改hosts文件 &#xff08;1&#xff09;定位hosts文件&#xff1a;首先&#xff0c;需要找到hosts文件的位置。它通常位于C:\Windows\System32\drivers\etc\目录下。 &#xff08;2&#xff09;以管理员身份运行记事本&#x…