网站建设的整个过程/论坛排名
手动实现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.java
的init()
方法的最后添加三行代码
//编写方法, 完成自己的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.修改MonsterController
的listMonster
方法
@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 参数封装到数组, 进行反射调用
修改ZzwDispatcherServlet
的executeDispatcher()
方法
//编写方法, 完成分发请求任务
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.去掉MonsterController
的findMonsterByName()
方法的name
字段的@RequestParam注解
//增加方法, 通过name返回对应的monster对象
@RequestMapping(value = "/monster/find")
public void findMonsterByName(HttpServletRequest request,HttpServletResponse response,String name) {.....
}
2.ZzwDispatcherServlet
的executeDispatcher()
的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.xml
的build节点
内插入以下代码 -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.在ZzwDispatcherServlet
的executeDispatcher
方法, 添加如下代码
//上面代码省略.../*** 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 解决方案: 在MonsterController
的login
方法, 形参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.ZzwDispatcherServlet
的executeDispatcher
, 在反射调用目标方法的位置扩展代码
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系列九: 数据格式化与验证及国际化
📚 目录导航 📚
- SpringMVC系列一: 初识SpringMVC
- SpringMVC系列二: 请求方式介绍
- SpringMVC系列三: Postman(接口测试工具)
- SpringMVC系列四: Rest-优雅的url请求风格
- SpringMVC系列五: SpringMVC映射请求数据
- SpringMVC系列六: 视图和视图解析器
- SpringMVC系列七: 手动实现SpringMVC底层机制-上
- SpringMVC系列八: 手动实现SpringMVC底层机制-下
…
💬 读者互动 💬
在学习SpringMVC底层机制的过程中,你有哪些疑问或需要帮助的地方?欢迎在评论区留言,我们一起讨论。
相关文章:

SpringMVC系列八: 手动实现SpringMVC底层机制-下
手动实现SpringMVC底层机制-下 实现任务阶段五🍍完成Spring容器对象的自动装配-Autowired 实现任务阶段六🍍完成控制器方法获取参数-RequestParam1.🥦将 方法的 HttpServletRequest 和 HttpServletResponse 参数封装到数组, 进行反射调用2.&a…...

【昇思初学入门】第八天打卡-模型保存与加载
模型保存与加载 学习心得 保存 CheckPoint 格式文件,在模型训练过程中,可以添加检查点(CheckPoint)用于保存模型的参数,以便进行推理及再训练使用。如果想继续在不同硬件平台上做推理,可通过网络和CheckPoint格式文件生成对应的…...

喜报!极限科技新获得一项国家发明专利授权:“搜索数据库的正排索引处理方法、装置、介质和设备”
近日,极限数据(北京)科技有限公司(简称:极限科技)新获得一项国家发明专利授权,专利名为 “搜索数据库的正排索引处理方法、装置、介质和设备”,专利号:ZL 2024 1 0479400…...

深入探讨:UART与USART在单片机中串口的实际应用与实现技巧
单片机(Microcontroller Unit, MCU)是一种集成了处理器、存储器和输入输出接口的微型计算机。它广泛应用于嵌入式系统中,用于控制各类电子设备。UART和USART是单片机中常见的通信接口,负责串行数据传输。下面我们详细介绍它们在单…...

Windows上PyTorch3D安装踩坑记录
直入正题,打开命令行,直接通过 pip 安装 PyTorch3D : (python11) F:\study\2021-07\python>pip install pytorch3d Looking in indexes: http://mirrors.aliyun.com/pypi/simple/ ERROR: Could not find a version that satisfies the requirement p…...

操作符详解(上) (C语言)
操作符详解(上) 一. 进制转换1. 二进制2. 二进制的转换 二. 原码 补码 反码三. 操作符的分类四. 结构成员访问操作符1. 结构体的声明2. 结构体成员访问操作符 一. 进制转换 1. 二进制 在学习操作符之前,我们先了解一些2进制、8进制、10进制…...

使用 audit2allow 工具添加SELinux权限的方法
1. audit2allow工具的使用 audit2allow 命令的作用是分析日志,并提供允许的建议规则或拒绝的建议规则。 1.1 audit2allow的安装 sudo apt-get install policycoreutilssudo apt install policycoreutils-python-utils 1.2 auditallow的命令 命令含义用法-v--ve…...

一文弄懂FPGA
一、FPGA简介 什么是FPGA? FPGA(Field-Programmable Gate Array)是一种可编程逻辑器件,可以在现场通过硬件描述语言(HDL)进行配置。它具有高度的灵活性和并行处理能力,广泛应用于通信、计算、…...

Rust 中使用 :: 这种语法的几种情况
文章目录 1. 访问模块成员:2. 访问关联函数或静态方法:3. 访问 trait 的关联类型或关联常量4. 指定泛型类型参数 1. 访问模块成员: mod utils {pub fn do_something() { /* ... */ } }let result utils::do_something();2. 访问关联函数或静…...

Ruby langchainrb gem and custom configuration for the model setup
题意:Ruby 的 langchainrb gem 以及针对模型设置的自定义配置 问题背景: I am working in a prototype using the gem langchainrb. I am using the module assistant module to implemente a basic RAG architecture. 我正在使用 langchainrb 这个 ge…...

高校新生如何选择最优手机流量卡?
一年一度的高考已经结束了,愿广大学子金榜题名,家长们都给孩子准备好了手机,那么手机流量卡应该如何选择呢? 高校新生在选择手机流量卡时,需要综合考量流量套餐、费用、网络覆盖、售后服务等多方面因素,以下…...

QT QML 生成二维码
Qt生成二维码 C++版 文章目录 步骤1:安装libqrencode步骤2:创建C++类生成二维码步骤3:将C++类与QML绑定步骤4:创建QML界面步骤5:配置项目文件总结在Qt QML中实现二维码生成,可以使用一个C++库来生成二维码,然后将生成的二维码图像传递给QML进行显示。一个常用的二维码生…...

IDEA中Maven--下载安装自己适配的版本---理解
Maven解释: Maven是一个强大的项目管理工具和构建工具,主要用于Java项目。它能够帮助开发团队管理项目的依赖、构建项目、发布文档和报告,并能够自动化许多重复的任务。 Maven的主要作用包括: 依赖管理:Maven能够管理…...

【osgEarth】Ubuntu 22.04 源码编译osgEarth 3.5
下载源代码 git clone --depth1 https://dgithub.xyz/gwaldron/osgearth -b osgearth-3.5 下载子模块 git submodule update --init 如果下载不过来,就手动修改下.git/config文件,将子模块的地址替换成加速地址 (base) yeqiangyeqiang-Default-string…...

ASP.NET Core 6.0 使用 资源过滤器和行为过滤器
1.AOP 面向切面编程 概念 AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,旨在通过预定义的模式(即“切面”)对程序的横切关注点进行模块化。横切关注点是一个在多个应用模块中出现的概念,例如日志记录、事务管理、安全检查等。AOP允许开发者定义“切面”…...

电脑屏幕花屏怎么办?5个方法解决问题!
“我刚刚打开电脑就发现我的电脑屏幕出现了花屏的情况。这让我很困惑,我应该怎么解决这个问题呢?求帮助。” 在这个数字时代的浪潮中,电脑早已成为我们生活中不可或缺的一部分。然而,当你正沉浸在紧张的游戏对战中,或是…...

git 初基本使用-----------笔记
Git命令 下载git 打开Git官网(git-scm.com),根据自己电脑的操作系统选择相应的Git版本,点击“Download”。 基本的git命令使用 可以在项目文件下右击“Git Bash Here” ,也可以命令终端下cd到指定目录执行初始化命令…...

Redis-数据类型-Bit的基本操作-getbit-setbit-Bitmap
文章目录 0、Bitmaps(位图)1、查看redis是否启动2、通过客户端连接redis3、切换到db7数据库4、设置(或覆盖)一个键(key)的值(value)5、获取存储在给定键(key)…...

统信UOS上鼠标右键菜单中添加自定义内容
原文链接:统信UOS上鼠标右键菜单中添加自定义内容 Hello,大家好啊!今天给大家带来一篇关于在统信UOS桌面操作系统上鼠标右键菜单中添加自定义内容的文章。通过自定义鼠标右键菜单,可以大大提升日常操作的效率和便捷性。本文将详细…...

学习入门 chatgpt原理 一
学习文章:人人都能看懂的chatGpt原理课 笔记作为学习用,侵删 Chatph和自然语言处理 什么是ChatGpt ChatGPT(Chat Generative Pre-training Transformer) 是一个 AI 模型,属于自然语言处理( Natural Lang…...

生命在于学习——Python人工智能原理(4.7)
四、Python的程序结构与函数 4.4 函数 函数能将代码划分为若干模块,每一个模块可以相对独立的实现某一个功能,函数有两个主要功能,分别是降低编程难度和实现代码复用,函数是一种功能抽象,复用它可以将一个复杂的大问…...

经典游戏案例:仿植物大战僵尸
学习目标:仿植物大战僵尸核心玩法实现 游戏画面 项目结构目录 部分核心代码 using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; using Random UnityEngine.Random;public enum…...

[Day 18] 區塊鏈與人工智能的聯動應用:理論、技術與實踐
強化學習與生成對抗網絡(GAN) 引言 強化學習 (Reinforcement Learning, RL) 和生成對抗網絡 (Generative Adversarial Networks, GANs) 是現代人工智能中的兩大關鍵技術。強化學習使得智能體可以通過與環境交互學習最佳行動策略,而生成對抗網絡則通過兩個相互競爭…...

【Mac】DMG Canvas for mac(DMG镜像制作工具)软件介绍
软件介绍 DMG Canvas 是一款专门用于创建 macOS 磁盘映像文件(DMG)的软件。它的主要功能是让用户可以轻松地设计、定制和生成 macOS 上的安装器和磁盘映像文件,以下是它的一些主要特点和功能。 主要特点和功能 1. 用户界面设计 DMG Canva…...

RAG分块方法 从固定大小到自然语言处理分块——深入研究文本分块技术
发掘文本分块-准确的搜索结果和更智能的语言模型背后的秘诀,通过了解如何有效地分块文本,我们可以改进索引文档、处理用户查询和利用搜索结果的方式。准备好揭开文本分块的秘密了吗? 一、了解分块 分块是一种旨在嵌入尽可能少噪音的内容,同…...

FFmpeg 系列
📚 此篇文章是先引入ffmpeg的概念以及主要的功能,后面会根据每一个特点进行详解,喜欢ffmpeg的可以持续关注。 ffmpeg是什么? FFmpeg 是一个开源的跨平台音视频处理工具,它可以用来录制、转换以及流化音视频内容。具体…...

240626_昇思学习打卡-Day8-稀疏矩阵
240626_昇思学习打卡-Day8-稀疏矩阵 稀疏矩阵 在一些应用场景中,比如训练二值化图像分割时,图像的特征是稀疏的,使用一堆0和极个别的1表示这些特征即费事又难看,此时就可以使用稀疏矩阵。通过参考大佬博文,结合个人理…...

Docker: 使用容器化数据库
使用容器化数据库 使用本地容器化数据库提供了灵活性和简易的设置,使您能够在不需要传统数据库安装开销的情况下,紧密模拟生产环境。Docker 简化了这一过程,只需几条命令就可以在隔离的容器中部署、管理和扩展数据库。 在本指南中,您将学习如何: 运行本地容器化数据库访…...

Oracle对用户敏感数据进行编码处理
由于系统运行时间比较长,没有对用户的身份证号、邮箱、手机号进行脱敏处理,后期对数据进行了编码。 更新表数据 sql UPDATE sys_staff SET MOBIL_PHONE CASEWHEN MOBIL_PHONE IS NULL THEN ELSE utl_raw.cast_to_varchar2(utl_encode.base64_encode(ut…...

VXLAN详解:概念、架构、原理、搭建过程、常用命令与实战案例
一、VXLAN概述 1.1 VXLAN的定义 VXLAN(Virtual Extensible LAN,虚拟可扩展局域网)是一种网络虚拟化技术,通过在现有IP网络上创建虚拟网络,使数据中心可以实现大规模的网络隔离和扩展。VXLAN使用MAC-in-UDP封装技术&a…...