SpringMVC底層機制簡單實作-04
https://github.com/liyuelian/springmvc-demo.git
8.任務7-完成簡單視圖決議
功能說明:通過目標方法回傳的 String,轉發或重定向到指定頁面
8.1分析
原生的 SpringMVC 使用視圖決議器來對 Handler 方法回傳的 String(該String會轉為視圖類)進行決議,然后轉發或重定向到指定頁面,
這里為了簡化,直接在自定義的前端控制器撰寫方法完成視圖決議器的功能,

8.2代碼實作
(1)修改 MyDispatcherServlet 的 executeDispatch 方法
部分代碼:
//撰寫方法,完成分發請求
private void executeDispatch(HttpServletRequest request, HttpServletResponse response) {
MyHandler myHandler = getMyHandler(request);
try {
//如果 myHandler為 null,說明請求 url沒有匹配的方法,即用戶請求的資源不存在
if (myHandler == null) {
response.getWriter().print("<h1>404 NOT FOUND</h1>");
} else {//匹配成功,就反射呼叫控制器的方法
//1.先獲取目標方法的所有形參的引數資訊
Class<?>[] parameterTypes = myHandler.getMethod().getParameterTypes();
//2.創建一個引數陣列(對應實引陣列),在后面反射調動目標方法時會用到
Object[] params = new Object[parameterTypes.length];
//遍歷形引陣列 parameterTypes,根據形引陣列的資訊,將實參填充到實引陣列中
//步驟一:將方法的 request 和 response 引數封裝到引數陣列,進行反射呼叫
for (int i = 0; i < parameterTypes.length; i++) {
//....
//....略
//....
}
//步驟二:將 http請求的引數封裝到 params陣列中[要注意填充實引陣列的順序問題]
//先處理中文亂碼問題
request.setCharacterEncoding("utf-8");
Map<String, String[]> parameterMap = request.getParameterMap();
// 遍歷 parameterMap,將請求引數按照順序填充到實引陣列 params
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
//....
//....略
//....
}
//反射呼叫目標方法
Object result = myHandler.getMethod()
.invoke(myHandler.getController(), params);
//對回傳的結果進行決議(原生的SpringMVC通過視圖決議器來完成)
if (result instanceof String) {
String viewName = (String) result;
System.out.println("viewName=" + viewName);
if (viewName.contains(":")) {//如果回傳的String結果為 forward:/login_ok.jsp
// 或 redirect:/login_ok.jsp 的形式
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)) {//重定向
//注意這里的路徑問題
viewPage = request.getContextPath() + viewPage;
response.sendRedirect(viewPage);
}
} else {//如果兩者都沒有,默認為請求轉發
request.getRequestDispatcher("/" + viewName)
.forward(request, response);
}
}//這里還可以拓展
}
} catch (Exception e) {
e.printStackTrace();
}
}
(2)創建測驗頁面和測驗方法
MonsterService 介面:
package com.li.service;
import com.li.entity.Monster;
import java.util.List;
/**
* @author 李
* @version 1.0
*/
public interface MonsterService {
//增加方法,處理登錄
public boolean login(String name);
}
MonsterServiceImpl 實作類:
package com.li.service.impl;
import com.li.entity.Monster;
import com.li.myspringmvc.annotation.Service;
import com.li.service.MonsterService;
import java.util.ArrayList;
import java.util.List;
/**
* @author 李
* @version 1.0
* MonsterServiceImpl 作為一個Service物件注入容器
*/
@Service
public class MonsterServiceImpl implements MonsterService {
@Override
public boolean login(String name) {
//模擬DB
if ("白骨精".equals(name)) {
return true;
} else {
return false;
}
}
}
MonsterController 控制器:
package com.li.controller;
import com.li.entity.Monster;
import com.li.myspringmvc.annotation.AutoWired;
import com.li.myspringmvc.annotation.Controller;
import com.li.myspringmvc.annotation.RequestMapping;
import com.li.myspringmvc.annotation.RequestParam;
import com.li.service.MonsterService;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
/**
* @author 李
* @version 1.0
* 用于測驗的 Controller
*/
@Controller
public class MonsterController {
//屬性
@AutoWired
private MonsterService monsterService;
//處理登錄的方法,回傳要請求轉發或重定向的字串
@RequestMapping(value = "https://www.cnblogs.com/monster/login")
public String login(HttpServletRequest request,
HttpServletResponse response,
@RequestParam(value = "https://www.cnblogs.com/liyuelian/archive/2023/02/13/monsterName") String mName) {
System.out.println("----接收到的mName-->" + mName);
request.setAttribute("mName", mName);
boolean b = monsterService.login(mName);
if (b) {//登錄成功
// 請求轉發到login_ok.jsp
//return "forward:/login_ok.jsp";
//return "redirect:/login_ok.jsp";
return "login_ok.jsp";
} else {//登錄失敗
//return "forward:/login_error.jsp";
//return "redirect:/login_error.jsp";
return "login_error.jsp";
}
}
}
在webapp目錄下分別創建 login.jsp,login_ok.jsp,login_error.jsp
login.jsp:
<%--
Created by IntelliJ IDEA.
User: li
Date: 2023/2/12
Time: 22:24
Version: 1.0
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登錄頁面</title>
</head>
<body>
<h1>登錄頁面</h1>
<form action="monster/login" method="post">
妖怪名:<input type="text" name="monsterName"><br/>
<input type="submit" value="https://www.cnblogs.com/liyuelian/archive/2023/02/13/登錄">
</form>
</body>
</html>
login_ok.jsp:
<%--
Created by IntelliJ IDEA.
User: li
Date: 2023/2/12
Time: 22:27
Version: 1.0
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
<title>登錄成功</title>
</head>
<body>
<h1>登錄成功</h1>
歡迎你:${requestScope.mName}
</body>
</html>
login_error.jsp:
<%--
Created by IntelliJ IDEA.
User: li
Date: 2023/2/12
Time: 22:28
Version: 1.0
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
<title>登錄失敗</title>
</head>
<body>
<h1>登錄失敗</h1>
sorry,登錄失敗 ${requestScope.mName}
</body>
</html>
(3)啟動 tomcat,訪問 http://localhost:8080/li_springmvc/login.jsp
測驗成功,
9.任務8-自定義@ResponseBody
9.1分析
功能說明:通過自定義@ResponseBody 注解,回傳 JSON格式資料
在實際開發中,前后端分離的專案,通常是直接json資料給客戶端/瀏覽器,客戶端接收到資料后,再自己決定如何處理和顯示,
9.2代碼實作
(1)@ResponseBody 注解
package com.li.myspringmvc.annotation;
import java.lang.annotation.*;
/**
* @author 李
* @version 1.0
* ResponseBody 注解用于指定目標方法是否要回傳指定格式的資料
* 如果value為默認值,或者value="https://www.cnblogs.com/liyuelian/archive/2023/02/13/json",認為目標方法要回傳的資料格式為json
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody {
String value() default "";
}
(2)修改 MyDispatcherServlet 的 executeDispatch 方法
//撰寫方法,完成分發請求
private void executeDispatch(HttpServletRequest request, HttpServletResponse response) {
MyHandler myHandler = getMyHandler(request);
try {
//如果 myHandler為 null,說明請求 url沒有匹配的方法,即用戶請求的資源不存在
if (myHandler == null) {
response.getWriter().print("<h1>404 NOT FOUND</h1>");
} else {//匹配成功,就反射呼叫控制器的方法
Class<?>[] parameterTypes = myHandler.getMethod().getParameterTypes();
//2.創建一個引數陣列(對應實引陣列),在后面反射調動目標方法時會用到
Object[] params = new Object[parameterTypes.length];
//遍歷形引陣列 parameterTypes,根據形引陣列的資訊,將實參填充到實引陣列中
//...
//...
//...
//...
//反射呼叫目標方法
Object result =
myHandler.getMethod().invoke(myHandler.getController(), params);
//對回傳的結果進行決議(原生的SpringMVC通過視圖決議器來完成)
if (result instanceof String) {
//....略
}//這里還可以拓展
else if (result instanceof ArrayList) {//如果是一個集合
Method method = myHandler.getMethod();
//判斷目標方法是否有一個@ResponseBody注解
if (method.isAnnotationPresent(ResponseBody.class)) {
String valueType = method.getAnnotation(ResponseBody.class).value();
//如果注解的為默認值,或者value="https://www.cnblogs.com/liyuelian/archive/2023/02/13/json",就認為目標方法要回傳的資料格式為json
if ("json".equals(valueType) || "".equals(valueType)) {
//對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();
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
(3)pom.xml檔案中引入jackson
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.4</version>
</dependency>
(4)MonsterController 測驗類增加方法測驗
/**
* 撰寫方法,回傳json格式的資料
* 1.目標方法回傳的結果是給SpringMVC底層通過反射呼叫的位置
* 2.我們在SpringMVC底層反射呼叫的位置接收到結果并進行決議即可
* 3. @ResponseBody(value = "https://www.cnblogs.com/liyuelian/archive/2023/02/13/json") 表示希望以json格式回傳資料給瀏覽器
* @param request
* @param response
* @return
*/
@RequestMapping(value = "https://www.cnblogs.com/monster/list/json")
@ResponseBody(value = "https://www.cnblogs.com/liyuelian/archive/2023/02/13/json")
public List<Monster> listMonsterByJson(HttpServletRequest request,
HttpServletResponse response) {
List<Monster> monsters = monsterService.listMonster();
return monsters;
}
(5)啟動 tomcat,瀏覽器訪問 http://localhost:8080/li_springmvc/monster/list/json,回傳如下結果,測驗成功,
10.小結

SpringMVC機制梳理
-
web.xml 中配置前端控制器(DispatcherServlet)和 spring 容器檔案
-
當啟動 tomcat 時,DispatcherServlet 被 tomcat 創建
-
前端控制器作業:
-
(1)創建 spring 容器并初始化(從 web.xml 檔案中獲取 spring組態檔名):
-
a. 掃描包,獲取要注入的類的全路徑,
-
b. 將掃描到的類進行反射,放入ioc容器,
-
c. 完成屬性自動裝配
-
-
(2)記錄控制器的目標方法和 url 的映射關系(在原生 SpringMVC 中,這個作業由 HandlerMapping 完成)
-
(3)完成分發請求:
-
a. 完成用戶 url 和控制器 url 的匹配以及目標方法的呼叫
-
b. 目標方法引數的自動賦值:對瀏覽器請求 url 的引數進行處理,考慮目標方法形參的多樣性,將其封裝到引數陣列,以反射呼叫的形式傳遞給目標方法
目標方法的實參是在 SpringMVC 底層通過封裝好的引數陣列傳入的
-
c. 反射目標方法,對目標方法回傳的結果進行決議(原生SpringMVC中,決議的作業由視圖決議器完成),決定請求轉發/重定向/回傳 json 格式的資料等
-
-
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/543742.html
標籤:其他
