day07-SpringMVC底層機制簡單實現-03

来源:https://www.cnblogs.com/liyuelian/archive/2023/02/12/17114729.html
-Advertisement-
Play Games

SpringMVC底層機制簡單實現-03 https://github.com/liyuelian/springmvc-demo.git 7.任務6-完成控制器方法獲取參數-@RequestParam 功能說明:自定義 @RequestParam 註解和方法參數名獲取參數。 當瀏覽器訪問 Handl ...


SpringMVC底層機制簡單實現-03

https://github.com/liyuelian/springmvc-demo.git

7.任務6-完成控制器方法獲取參數-@RequestParam

功能說明:自定義 @RequestParam 註解和方法參數名獲取參數。

當瀏覽器訪問 Handler 方法時,如果 url 帶有參數,可以通過自定義的 @RequestParam 註解來獲取該參數,將其值賦給 Handler 方法中該註解修飾的形參。如:

url=http://ip:port/web工程路徑/monster/find?name=孫悟空

@RequestMapping(value = "/monster/find")
public void findMonstersByName(HttpServletRequest request,HttpServletResponse response,
@RequestParam(value = "name") String username) {
    //註解的 value 值要和 url 的參數名一致
    //代碼....
}

7.1分析

之前是通過自定義的前端控制器 MyDispatcherServlet 來完成分發請求:所有的請求都通過 doGet 和 doPost 來調用 executeDispatch() 方法,在 executeDispatch() 方法中,通過反射調用控制器的方法。

原先的 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 {//匹配成功,就反射調用控制器的方法
            myHandler.getMethod().invoke(myHandler.getController(), request, response);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

但是由於 Handler 業務方法的形參個數、種類的不同,因此在反射的時候要考慮目標方法形參多種形式的問題。

Method 類的 invoke() 方法如下,它支持可變參數。

image-20230211225322448

因此解決辦法是:將需要傳遞給目標方法的實參,封裝到一個參數數組,然後以反射調用的方式傳遞給目標方法。

控制器方法用來接收前端數據的參數,除了request 和 response,其他參數一般都是使用 String 類型來接收的,因此目標方法形參可能有兩種情況:

  1. HttpServletRequest 和 HttpServletResponse 參數
  2. 接收的是String類型的參數
    • 指定 @RequestParam 的 String 參數
    • 沒有指定 @RequestParam 的 String 參數

因此需要將上述兩種形參對應的實參分別封裝到實參數組,進行反射調用:

怎麼將需要傳遞給目標方法的實參,封裝到一個參數數組?答:獲取當前目標方法的所有形參信息,遍歷這個形參數組,根據形參數組的下標索引,將實參填充到實參數組對應的下標索引中。

(1)將方法的 HttpServletRequest 和 HttpServletResponse 參數封裝到參數數組

(2)將方法指定 @RequestParam 的 String 參數封裝到參數數組

(3)將方法中沒有指定 @RequestParam 的String 參數按照預設參數名封裝到參數數組

7.2代碼實現

(1)@RequestParam註解

package com.li.myspringmvc.annotation;

import java.lang.annotation.*;

/**
 * @author 李
 * @version 1.0
 * RequestParam 註解標註在目標方法的參數上,表示映射http請求的參數
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
    String value() default "";
}

(2)MyDispatcherServlet 中修改 executeDispatch() 方法,並增加兩個方法 getIndexOfRequestParameterIndex() 和 getParameterNames()。

部分代碼:

//編寫方法,完成分發請求
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.原先的寫法為 myHandler.getMethod()
             *      .invoke(myHandler.getController(), request, response);
             *  它的局限性是目標方法只能有兩個形參: HttPServletRequest 和 HttPServletResponse
             * 2.改進:將需要request的實參,封裝到一個參數數組,然後以反射調用的方式傳遞給目標方法
             * 3.public Object invoke(Object obj, Object... args)
             */
            //1.先獲取目標方法的所有形參的參數信息
            Class<?>[] parameterTypes = myHandler.getMethod().getParameterTypes();
            //2.創建一個參數數組(對應實參數組),在後面反射調動目標方法時會用到
            Object[] params = new Object[parameterTypes.length];
            //遍歷形參數組 parameterTypes,根據形參數組的信息,將實參填充到實參數組中

            //步驟一:將方法的Request和Response參數封裝到實參數組,進行反射調用
            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數組中[要註意填充實參數組的順序問題]
            // 獲取http請求的參數集合 Map<String, String[]>
            // 第一個參數 String 表示 http請求的參數名,
            // 第二個參數 String[]數組,之所以為數組,是因為前端有可能傳入像checkbox這種多選的參數
            Map<String, String[]> parameterMap = request.getParameterMap();
            // 遍歷 parameterMap,將請求參數按照順序填充到實參數組 params
            for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
                //取出請求參數的名
                String name = entry.getKey();
                //取出請求參數的值(為了簡化,只考慮參數是單值的情況,不考慮類似checkbox提交的數據)
                String value = entry.getValue()[0];
                //找到請求的參數對應目標方法的形參的索引,然後將其填充到實參數組
                //1.[請求參數名和 @RequestParam 註解的 value值 匹配]
                int indexOfRequestParameterIndex =
                        getIndexOfRequestParameterIndex(myHandler.getMethod(), name);
                if (indexOfRequestParameterIndex != -1) {//找到了對應位置
                    //將請求參數的值放入實參數組中
                    params[indexOfRequestParameterIndex] = value;
                } else {
                    //沒有在目標方法的形參數組中找到對應的下標位置
                    //2.使用預設機制進行匹配 [即請求參數名和形參名匹配]
                    // (1)拿到目標方法的所有形參名
                    List<String> parameterNames =
                        getParameterNames(myHandler.getMethod());
                    // (2)對形參名進行遍歷,如果匹配,把當前請求的參數值填充到實參數組的相同索引位置
                    for (int i = 0; i < parameterNames.size(); i++) {
                        //如果形參名和請求的參數名相同
                        if (name.equals(parameterNames.get(i))) {
                            //將請求的參數的value值放入實參數組中
                            params[i] = value;
                            break;
                        }
                    }
                }
            }
            myHandler.getMethod().invoke(myHandler.getController(), params);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
 * 編寫方法,返回請求參數是目標方法的第幾個形參
 * [請求參數名和 @RequestParam 註解的 value值 匹配]
 *
 * @param method 目標方法
 * @param name   請求的參數名
 * @return 返回請求的參數匹配目標方法形參的索引位置
 */
public int getIndexOfRequestParameterIndex(Method method, String name) {
    //得到 method的所有形參參數
    Parameter[] parameters = method.getParameters();
    for (int i = 0; i < parameters.length; i++) {
        //取出當前的形參
        Parameter parameter = parameters[i];
        //先處理前面有 @RequestParam 註解修飾的形參
        if (parameter.isAnnotationPresent(RequestParam.class)) {
            //取出當前形參parameter的註解 @RequestParam的 value值
            String value = parameter.getAnnotation(RequestParam.class).value();
            //將請求的參數和註解指定的value匹配,如果相同就說明找到了目標方法的形參位置
            if (name.equals(value)) {
                return i;//返回的是匹配的形參的位置
            }
        }
    }
    return -1;//如果沒有匹配成功,就返回-1
}

/**
 * 編寫方法,得到目標方法的所有形參的名稱,並放入到集合中返回
 *
 * @param method
 * @return
 */
public List<String> getParameterNames(Method method) {
    ArrayList<String> paramNamesList = new ArrayList<>();
    //獲取到所有的參數名--->這裡有一個細節
    //預設情況下 parameter.getName() 返回的的名稱不是真正的形參名 request,response,name...
    //而是 [arg0, arg1, arg2...]
    //這裡我們使用java8的特性,並且在pom.xml文件中配置maven編譯插件,才能得到真正的名稱
    Parameter[] parameters = method.getParameters();
    //遍歷parameters,取出名稱,放入 paramNamesList
    for (Parameter parameter : parameters) {
        paramNamesList.add(parameter.getName());
    }
    System.out.println("目標方法的形參參數列表名稱=" + paramNamesList);
    return paramNamesList;
}

(3)在pom.xml文件中引入插件

點擊 maven 管理,clean 項目,再重啟一下 tomcat,防止引入出現問題

<build>
  <pluginManagement>
    <plugins>
        <plugin>...</plugin>
        <plugin>...</plugin>
        <!--引入插件-->
        <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>
  </pluginManagement>
</build>

(4)編寫方法測試

MonsterService 介面:

package com.li.service;

import com.li.entity.Monster;

import java.util.List;

/**
 * @author 李
 * @version 1.0
 */
public interface MonsterService {
    //增加方法,通過傳入的名字返回 monster列表
    public List<Monster> findMonsterByName(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 List<Monster> findMonsterByName(String name) {
        //這裡模擬到 DB獲取數據
        List<Monster> monsters = new ArrayList<>();
        monsters.add(new Monster(100, "牛魔王", "芭蕉扇", 400));
        monsters.add(new Monster(200, "貓妖", "撕咬", 800));
        monsters.add(new Monster(300, "鼠精", "偷燈油", 200));
        monsters.add(new Monster(400, "大象精", "運木頭", 300));
        monsters.add(new Monster(500, "白骨精", "吐煙霧", 500));

        //創建集合返回查詢到的monster集合
        List<Monster> findMonsters = new ArrayList<>();
        //遍歷monster集合,將符合條件的放到findMonster集合中
        for (Monster monster : monsters) {
            if (monster.getName().contains(name)) {
                findMonsters.add(monster);
            }
        }
        return findMonsters;
    }
}

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;

    //增加方法,通過name返回對應的monster集合
    @RequestMapping(value = "/monster/find")
    public void findMonsterByName(HttpServletRequest request, 
                                  HttpServletResponse response,
                                  @RequestParam(value = "name") String monsterName) {
        //設置編碼
        response.setContentType("text/html;charset=utf-8");

        System.out.println("----接收到的name=" + monsterName);
        StringBuilder content = new StringBuilder("<h1>妖怪列表信息</h1>");
        content.append("<table border='1px' width='400px' style='border-collapse:collapse'>");
        //調用 monsterService的方法
        List<Monster> monsters = monsterService.findMonsterByName(monsterName);
        for (Monster monster : monsters) {
            content.append("<tr>" +
                    "<td>" + monster.getId() + "</td>" +
                    "<td>" + monster.getName() + "</td>" +
                    "<td>" + monster.getSkill() + "</td>" +
                    "<td>" + monster.getAge() + "</td></tr>");
        }
        content.append("</table>");
        //獲取writer,返回提示信息
        try {
            PrintWriter printWriter = response.getWriter();
            printWriter.print(content.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

(5)重啟 tomcat,瀏覽器訪問url= http://localhost:8080/li_springmvc/monster/find?name=牛魔王,顯示如下:

image-20230212205108104

後端輸出:

----接收到的name=牛魔王

情況二:如果目標方法沒有使用 @RequestParam 註解修飾:

image-20230212205412461

redeployTomcat,訪問相同的url,仍然可以接收到參數,並顯示頁面。測試成功。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • DuckDB 是近年來頗受關註的OLAP資料庫,號稱是OLAP領域的SQLite,以精巧簡單,性能優異而著稱。筆者前段時間在調研Doris的Pipeline的運算元並行方案,而DuckDB基於論文《Morsel-Driven Parallelism: A NUMA-Aware Query Evalua ...
  • 重新總結組件的定義 這是官方對組件的定義:組件允許我們將 UI 劃分為獨立的、可重用的部分,並且可以對每個部分進行單獨的思考。在實際應用中,組件常常被組織成層層嵌套的樹狀結構。 對於 Vue 開發經驗不多的我來說,起初我只是簡單的把一個組件當作一個頁面,也並沒有把頁面中太多的可以獨立劃分的地方寫成組 ...
  • vuex的原理是什麼? 它採用 集中式存儲管理 應用的所有組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。 每一個 Vuex 應用的核心就是 store,裡面又包括: (1)state(數據):用來存放數據源,就是公共狀態; (2)getters(數據加工):有的時候需要對數據源進行加 ...
  • 使用JS的DOM(文檔對象模型)獲取前端迴圈的參數 使用Go語言渲染html,但是想讓網頁動起來,顯示一些彈窗還是比較麻煩的,於是乎,想到使用js獲取頁面的數據進行顯示,但是js無法載入go的一些變數。想了很久,突然在網頁調試的時候使用了js的DOM進行元素查找獲得了些許靈感最後實現了這個功能。 1 ...
  • 初探富文本之CRDT協同演算法 CRDT的英文全稱是Conflict-free Replicated Data Type,最初是由協同文本編輯和移動計算而發展的,現在還被用作線上聊天系統、音頻分發平臺等等。當前CRDT演算法在富文本編輯器領域的協同依舊是典型的場景,常用於作為實現文檔協同的底層演算法,支持 ...
  • 0x001 自定義參數 $extra_param = "Some extra param"; Hook::add('response_end', function($params) use ($extra_param) { Log::info('Extra: '.$extra_param); }); ...
  • 官網: https://spring.io/ 更多spring cloud zookeeper 參考 https://docs.spring.io/spring-cloud-zookeeper/docs/current/reference/html 左側菜單 向下找到 spring Cloud Zo ...
  • 這篇文章主要介紹分散式系統中的非集中式結構,以及我們經常使用的三種基於非集中式架構的解決方案:Akka集群、Redis集群和Cassandra集群。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...