手动实现SpringMVC底层机制-下
- 实现任务阶段五
- 🍍完成Spring容器对象的自动装配-@Autowired
- 实现任务阶段六
- 🍍完成控制器方法获取参数-@RequestParam
- 1.🥦将 方法的 HttpServletRequest 和 HttpServletResponse 参数封装到数组, 进行反射调用
- 2.🥦在方法形参处, 指定 @RequestParam, 将对应的实参封装到参数数组, 进行反射调用
- 3.🥦在方法形参 没有指定 @RequestParam, 按照默认参数名获取值, 进行反射调用
- 实现任务阶段七
- 🍍完成简单视图解析
- 实现任务阶段八
- 🍍完成返回JSON格式数据-@ResponseBody
- 🥦分析+代码实现
- 🥦完成测试
在本篇文章中,我们将继续深入探讨如何手动实现SpringMVC的底层机制。通过这两部分的学习,你将全面理解SpringMVC的工作原理。
⬅️ 上一讲: SpringMVC系列七: 手动实现SpringMVC底层机制-上
🔧 需要用到的项目: zzw-springmvc项目
实现任务阶段五
🍍完成Spring容器对象的自动装配-@Autowired
说明: 完成Spring容器中对象的注入/自动装配
示意图[分析说明]
分析:
加入@Autowired
注解, 进行对象属性的装配. -如图
测试:
浏览器输入 http://localhost:8080/monster/list
, 返回列表信息
代码实现:
1.在com.zzw.zzwspringmvc.annotation
下新建@Autowired
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
String value() default "";
}
1.1MonsterController
添加属性monsterService
, 标注@Autowired
@Controller
public class MonsterController {
//@Autowired表示要完成属性的装配.
@Autowired
private MonsterService monsterService;
.....
}
2.ZzwWebApplicationContext
增加方法executeAutowired()
//编写方法, 完成属性的自动装配
public void executeAutowired() {
//判断ioc有没有要装配的对象
if (ioc.isEmpty()) {
return;//你也可以抛出异常, throw new RuntimeException("ioc 容器没有bean对象")
}
//遍历ioc容器中的所有注入的bean对象, 然后获取到bean的所有字段/属性, 判断是否需要装配
/**
* entry => <String, Object> -> String 就是你注入对象时的名称, Object就是bean对象
*/
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
//String key = entry.getKey();
Object bean = entry.getValue();
//获取bean的所有字段/属性
Field[] declaredFields = bean.getClass().getDeclaredFields();
for (Field declaredField : declaredFields) {
//判断当前这个字段, 是否有@Autowired
if (declaredField.isAnnotationPresent(Autowired.class)) {//有@Autowired
//得到当前这个字段的@Autowired
Autowired autowiredAnnotation = declaredField.getDeclaredAnnotation(Autowired.class);
String beanName = autowiredAnnotation.value();
if ("".equals(beanName)) {//如果没有设置value, 按照默认规则
//即得到字段类型名称的首字母小写, 作为名字来进行装配
Class<?> type = declaredField.getType();
beanName = type.getSimpleName().substring(0, 1).toLowerCase()
+ type.getSimpleName().substring(1);
}
//如果设置了value, 直接按照beanName来装配
//从ioc容器中获取bean
if (ioc.get(beanName) == null) {//说明你指定的名字对应的bean不在ioc容器
throw new RuntimeException("ioc容器中, 不存在你要装配的bean");
}
//防止属性是private, 我们需要暴力破解
declaredField.setAccessible(true);
try {
declaredField.set(bean, ioc.get(beanName));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
}
3.ZzwWebApplicationContext.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.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.修改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>");//单线程使用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 参数封装到数组, 进行反射调用
修改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) {
//这里我们模拟数据->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.去掉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.对得到的目标方法的所有形参名进行遍历, 如果匹配就把当前请求的参数值, 填充到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.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 {
....
@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.在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.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 解决方案: 在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";
//测试默认方式-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.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底层机制的过程中,你有哪些疑问或需要帮助的地方?欢迎在评论区留言,我们一起讨论。