SpringMVC底層機制簡單實現-04
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 = "/monster/login")
public String login(HttpServletRequest request,
HttpServletResponse response,
@RequestParam(value = "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="登入">
</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="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="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 = "json") 表示希望以json格式返回資料給瀏覽器
* @param request
* @param response
* @return
*/
@RequestMapping(value = "/monster/list/json")
@ResponseBody(value = "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 格式的資料等
-
-