手动实现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底层机制的过程中,你有哪些疑问或需要帮助的地方?欢迎在评论区留言,我们一起讨论。