手动实现SpringMVC底层机制-第四阶段
在本篇文章中,我们将继续深入探讨如何手动实现SpringMVC的底层机制。通过这两部分的学习,你将全面理解SpringMVC的工作原理。
⬅️ 上一讲 : SpringMVC系列七: 手动实现SpringMVC底层机制-上
🔧 需要用到的项目 : zzw-springmvc项目
实现任务阶段七
🍍完成简单视图解析
功能说明: 通过方法返回的String, 转发
或者重定向
到指定页面
●完成任务说明
-用户输入白骨精, 可以登陆成功, 否则失败
-根据登陆的结果, 可以重定向或者请求转发到 login_ok.jsp / login_error.jsp
, 并显示妖怪名
-思路分析示意图
1.在webapp
目录下新建
1)login.jsp
html
<%@ 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
html
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录成功</title>
</head>
<body>
<h1>登陆成功</h1>
欢迎你: ${?}
</body>
</html>
3)login_error.jsp
html
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录失败</title>
</head>
<body>
<h1>登陆失败</h1>
sorry, 登陆失败 ${?}
</body>
</html>
2.MonsterService
增加一个方法login
java
public interface MonsterService {
....
//增加方法, 处理登录
public boolean login(String name);
}
MonsterServiceImpl
实现它
java
@Service
public class MonsterServiceImpl implements MonsterService {
....
@Override
public boolean login(String name) {
//实际上会到DB验证->这里我们模拟一下
if ("白骨精".equals(name)) {
return true;
} else {
return false;
}
}
}
3.MonsterController
添加一个方法login
java
//处理妖怪登陆的方法, 返回要请求转发/重定向的字符串
@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"
html
<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");
即可
java
//2.返回的Map<String, String[]> String: 表示http请求的参数名
// String[]: 表示http请求的参数值, 为什么是数组
//处理提交的数据中文乱码问题
request.setCharacterEncoding("utf-8");
Map<String, String[]> parameterMap = request.getParameterMap();
测试
5.在ZzwDispatcherServlet
的executeDispatcher
方法, 添加如下代码
java
//上面代码省略...
/**
* 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域
java
//处理妖怪登陆的方法, 返回要请求转发/重定向的字符串
@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
html
<%@ 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
html
<%@ 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
方法
java
//处理妖怪登陆的方法, 返回要请求转发/重定向的字符串
@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
方法
java
//处理妖怪登陆的方法, 返回要请求转发/重定向的字符串
@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
, 还会不会登陆成功?
html
<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")
java
//处理妖怪登陆的方法, 返回要请求转发/重定向的字符串
@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
java
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody {
String value() default "";
}
2.MonsterController
添加方法listMonsterByJson
java
@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
java
<!--引入jackson, 使用它的工具类可以进行json操作-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.4</version>
</dependency>
3.1com.zzw
包下新建一个测试类ZzwTest
java
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
, 在反射调用目标方法的位置扩展代码
java
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底层机制的过程中,你有哪些疑问或需要帮助的地方?欢迎在评论区留言,我们一起讨论。