Spring MVC深入理解之源碼實現

1、SpringMVC的理解

1)談談對Spring MVC的了解

MVC 是模型(Model)、視圖(View)、控制器(Controller)的簡寫,其核心思想是通過將業務邏輯、數據、顯示分離來組織代碼。

  1. Model:數據模型,JavaBean的類,用來進行數據封裝。

  2. View:指JSP、HTML用來展示數據給用戶

  3. Controller:用來接收用戶的請求,整個流程的控制器。用來進行數據校驗等

2)Spring MVC的核心組件

DispatcherServlet核心的中央處理器,負責接收請求、分發,并給予客戶端響應。

HandlerMapping處理器映射器,根據 URL 去匹配查找能處理的 Handler ,并會將請求涉及到的攔截器和 Handler 一起封裝。

HandlerAdapter處理器適配器,根據 HandlerMapping 找到的 Handler ,適配執行對應的 Handler

Handler請求處理器,處理實際請求的處理器。

ViewResolver視圖解析器,根據 Handler 返回的邏輯視圖 / 視圖,解析并渲染真正的視圖,并傳遞給 DispatcherServlet 響應客戶端

3)Spring MVC的工作流程

Spring MVC的工作流程如下:

  1. 用戶發送請求至前端控制器DispatcherServlet

  2. DispatcherServlet收到請求調用處理器映射器HandlerMapping。

  3. 處理器映射器根據請求url找到具體的處理器,生成處理器執行鏈HandlerExecutionChain(包括處理器對象和處理器攔截器)一并返回給DispatcherServlet。

  4. DispatcherServlet根據處理器Handler獲取處理器適配器HandlerAdapter執行HandlerAdapter處理一系列的操作,如:參數封裝,數據格式轉換,數據驗證等操作

  5. 執行處理器Handler(Controller,也叫頁面控制器)。

  6. Handler執行完成返回ModelAndView

  7. HandlerAdapter將Handler執行結果ModelAndView返回到DispatcherServlet

  8. DispatcherServlet將ModelAndView傳給ViewReslover視圖解析器

  9. ViewReslover解析后返回具體View

  10. DispatcherServlet對View進行渲染視圖(即將模型數據model填充至視圖中)。

  11. DispatcherServlet響應用戶。

2、代碼實現

1)測試代碼

import com.heaboy.mvc.XxhhMvc;public class Main {static {String path = Main.class.getResource("").getPath();String packageName = Main.class.getPackage().getName();XxhhMvc.scanner(path,packageName);}public static void main(String[] args) {XxhhMvc.exec("","");XxhhMvc.exec("test","index1");XxhhMvc.exec("test","");XxhhMvc.exec("test","asdfasdfasdf");XxhhMvc.exec("test","");}
}

輸出結果:

index -> index
test->index1
test->index
沒有這個方法 404
test->index

2)定義注解

import java.lang.annotation.*;/*** controller聲明*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @ interface Controller {
}
  1. @Documented
    • 這個元注解表明@Controller注解應該被javadoc或類似的工具記錄。也就是說,當你在編寫Java文檔時,@Controller注解的信息會被包含在生成的文檔中。這對于理解代碼中的注解用途非常有幫助。
  2. @Retention(RetentionPolicy.RUNTIME)
    • 這個元注解指定了@Controller注解的保留策略。RetentionPolicy.RUNTIME意味著這個注解在運行時仍然保留,因此它可以通過反射(Reflection)被讀取。這對于那些需要在運行時通過注解來獲取信息或行為的框架(如Spring MVC)來說是非常重要的。
  3. @Target(ElementType.TYPE)
    • 這個元注解指定了@Controller注解可以應用的Java元素類型。ElementType.TYPE表明這個注解只能用于類、接口(包括注解類型)或枚舉聲明上。這意味著你不能將這個注解用于方法、字段等其他元素上。
  4. public @interface Controller
    • 這行聲明了@Controller是一個注解(Annotation),并且它是公開的(public),意味著它可以被任何其他類訪問。@interface關鍵字用于聲明注解類型,與聲明接口(interface)類似,但注解(Annotation)不包含方法實現。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface RequestMapping {/**** @return*/String value() default "";
}
  1. @Documented
    • 如前所述,這個元注解表明?@RequestMapping?注解應該被 javadoc 或類似的工具記錄。這有助于在生成的文檔中包含注解的信息,從而幫助開發者理解代碼中的注解用途。
  2. @Retention(RetentionPolicy.RUNTIME)
    • 這個元注解指定了?@RequestMapping?注解的保留策略為?RUNTIME,意味著這個注解在運行時仍然保留,因此它可以通過反射被讀取。這對于 Spring MVC 框架在運行時解析注解信息并映射請求到相應的處理器方法至關重要。
  3. @Target({ElementType.TYPE, ElementType.METHOD})
    • 這個元注解指定了?@RequestMapping?注解可以應用的 Java 元素類型。在這個例子中,它指定了注解可以應用于類(TYPE)和方法(METHOD)上。這允許開發者在類級別上定義基礎的請求路徑,然后在方法級別上進一步細化這個路徑。
  4. public @interface RequestMapping
    • 這行聲明了?@RequestMapping?是一個公開的注解類型。
  5. String value() default "";
    • 這是?@RequestMapping?注解的一個屬性(也稱為元素)。它定義了請求的 URL 路徑模式。value?是這個屬性的名稱,而?default ""?表示如果在使用注解時沒有指定?value?屬性,它將默認為空字符串。但是,在 Spring MVC 中,更常見的做法是使用?path?屬性(盡管?value?和?path?在?@RequestMapping?中是等價的,可以互換使用)。

3)SpringMVC核心類

第一步:掃描并注冊MVC組件

static {String path = Main.class.getResource("").getPath();String packageName = Main.class.getPackage().getName();SpringMvc.scanner(path,packageName);}

首先獲取當前類的路徑和包名,然后根據當前類路徑和包名進行SpringMvc組件掃描

SpringMvc首先定義兩個全局HashMap如下:

 private static HashMap<String, Map<String,Method>> map=new HashMap<>();    
//用來存儲類上的@RequestMapping的值->(方法上的@RequestMapping的值->對應的方法)private static HashMap<String, Object> objMap=new HashMap<>();     
//用來存儲類上的@RequestMapping的值->對應類的實例

SpringMvc類的scanner方法如下:?

public static void scanner(String path,String packageName){List<String> paths = traverseFolder2(path);for (String p : paths) {p=p.substring(path.length()-1);    //得到文件名try {String className=packageName+"."+p.replaceAll( Matcher.quoteReplacement(File.separator),".");   //得到包名加文件名String replace = className.replace(".class", "");    //得到去掉.class后綴的包全限定名Class<?> cl = ClassLoader.getSystemClassLoader().loadClass(replace);    //獲取類的class對象if(isController(cl)){if(isRequestMapping(cl)){RequestMapping requestMapping = getRequestMapping(cl);if(map.containsKey(requestMapping.value())){throw  new RuntimeException("類多注解值:"+requestMapping.value());}else {map.put(requestMapping.value(),new HashMap<>());objMap.put(requestMapping.value(),cl.newInstance());}Method[] declaredMethods = cl.getDeclaredMethods();for (Method declaredMethod : declaredMethods) {if(isRequestMapping(declaredMethod)){RequestMapping mapping = getRequestMapping(declaredMethod);if(map.get(requestMapping.value()).containsKey(mapping.value())){throw  new RuntimeException("方法多注解值:"+requestMapping.value());}else {map.get(requestMapping.value()).put(mapping.value(),declaredMethod);}}}}else {throw  new RuntimeException("類無requestMapping");}}} catch (ClassNotFoundException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();}}}

首先根據路徑遍歷文件夾,找到所有的class文件,將類文件路徑列表放到paths中

private static List<String> traverseFolder2(String path) {File file = new File(path);List<String> classFiles=new ArrayList<>();if (file.exists()) {LinkedList<File> list = new LinkedList<File>();File[] files = file.listFiles();for (File file2 : files) {if (file2.isDirectory()) {list.add(file2);} else {classFiles.add(file2.getAbsolutePath());}}File temp_file;while (!list.isEmpty()) {temp_file = list.removeFirst();files = temp_file.listFiles();for (File file2 : files) {if (file2.isDirectory()) {list.add(file2);} else {classFiles.add(file2.getAbsolutePath());}}}} else {}return classFiles;}

然后處理每個類文件路徑:對于paths列表中的每個路徑p,代碼執行以下操作:

  • 提取文件名,得到xxx.class

  • 構造全限定類名:通過將包名packageName、文件分隔符替換為點(.),以及去除.class后綴,來構造類的全限定名。

  • 加載類:使用ClassLoader.getSystemClassLoader().loadClass(replace);加載類。

  • 檢查是否為控制器isController(cl)方法檢查該類是否是一個控制器(即是否有@Controller注解)。

private static boolean isController(Class cl){Annotation annotation = cl.getAnnotation(Controller.class);if(annotation!=null){return  true;}return false;}
  • 檢查類上是否有@RequestMapping注解isRequestMapping(cl)方法。

 private static boolean isRequestMapping(Class cl){Annotation annotation = cl.getAnnotation(RequestMapping.class);if(annotation!=null){return  true;}return false;}
  • 處理類上的@RequestMapping:如果類上有@RequestMapping注解,則提取其值(通常是URL路徑模式),并檢查是否已經在映射中注冊了相同的值。如果沒有,則將該類的實例(通過cl.newInstance())和該類的方法映射添加到相應的?objMap映射中。

  • 處理類中的方法:遍歷類的所有聲明的方法,檢查它們是否也有@RequestMapping注解。如果有,則將這些方法映射到它們各自的URL路徑上,在map中存儲。

private  static boolean isRequestMapping(Method method){Annotation annotation = method.getAnnotation(RequestMapping.class);if(annotation!=null){return  true;}return false;}
  • 異常處理:如果在處理過程中發現任何問題(如類加載失敗、類沒有@RequestMapping注解、或者同一個URL路徑被多個類或方法注冊),則拋出RuntimeException

第二步:使用類路徑和方法路徑找到對應的controller執行方法

 public static void exec(String classPath,String methodPath){if(objMap.get(classPath)==null){System.out.println("沒有這個類 404");}else {if(map.get(classPath).get(methodPath)==null){System.out.println("沒有這個方法 404");}else {try {map.get(classPath).get(methodPath).invoke(objMap.get(classPath));} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}}}}

由于在第一步已經將帶有@Controller注解的類中帶有@RequestMapping注解的類和其方法都存儲在objMap和map中,這一步直接在其尋找,如存在該映射路徑,則直接利用反射調用對應方法執行

本案例提供了兩個controller,如下

import com.xxhh.annotation.Controller;
import com.xxhh.annotation.RequestMapping;@Controller
@RequestMapping("test")
public class TestController {@RequestMappingpublic  String index(){System.out.println("test->index");return "";}@RequestMapping("index1")public  String index1(){System.out.println("test->index1");return "";}
}
import com.xxhh.annotation.Controller;
import com.xxhh.annotation.RequestMapping;@Controller
@RequestMapping
public class IndexController {@RequestMappingpublic  void index(){System.out.println("index -> index");}
}

最終執行結果如下:

index -> index
test->index1
test->index
沒有這個方法 404
test->index

以上就是簡單實現SpringMVC的全部源碼,如有錯誤,歡迎指正!!!?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/42342.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/42342.shtml
英文地址,請注明出處:http://en.pswp.cn/web/42342.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【cocos2dx】【iOS工程】如何保存用戶在游戲內的繪畫數據,并將數據以圖像形式展示在預覽界面

【cocos2dx】【iOS工程】如何保存用戶在應用內的操作數據&#xff0c;并將數據以圖像形式展示在預覽界面 設備/引擎&#xff1a;Mac&#xff08;11.6&#xff09;/Mac Mini 開發工具&#xff1a;Xcode&#xff08;15.0.1&#xff09; 開發需求&#xff1a;如何保存用戶在應用…

富格林:抓住正規穩健出金思路

富格林指出&#xff0c;凡事要學會抓住正規思路避繁就簡&#xff0c;才會順利達到終點。在現貨黃金市場中&#xff0c;投資者必須學會抓對正規趨勢&#xff0c;才是走向盈利出金的根本保障。以下是富格林投資總結的幾個觀點和建議&#xff0c;希望能幫助投資者實現穩健出金。 …

算法基礎之分治法

算法原理 對于一個規模為 n n n 的子問題&#xff0c;若該問題可以容易地解決則直接解決&#xff0c;否則將其分解為 k k k 個規模較小的子問題&#xff0c;這些子問題相互獨立且與原問題形式相同。遞歸地解決這些子問題&#xff0c;然后將各子問題的解合并得到原問題的解&a…

單鏈表詳解(2)

三、函數定義 查找節點 //查找結點 SLTNode* SLTNodeFind(SLTNode* phead, SLTDataType x) {assert(phead);SLTNode* pcur phead;while (pcur){if (pcur->data x){return pcur;}pcur pcur->next;}return NULL; } 查找節點我們是通過看數據域來查找的&#xff0c;查…

Arm64 基礎指令集介紹

按照字母排序順序&#xff1a; ● ADC&#xff1a;帶進位加法。 ● ADCS&#xff1a;帶進位加法&#xff0c;設置標志位。 ● ADD (extended register)&#xff1a;擴展寄存器加法。 ● ADD (immediate)&#xff1a;立即數加法。 ● ADD (shifted register)&#xff1a;移位寄存…

【MySQL05】【 undo 日志】

文章目錄 一、前言二、undo 日志&#xff08;回滾日志&#xff09;1. 事務 id2. undo 日志格式2.1 INSERT 對應的 undo 日志2.2 DELETE 對應的 undo 日志2.3 UPDATE 對應的 undo 日志2.3.1 不更新主鍵2.3.2 更新主鍵 2.3 增刪改操作對二級索引的影響2.4 roll_pointer 3. FIL_PA…

Windows 網絡重置

netsh int ip reset 命令是用于重置 Windows 操作系統中的網絡設置和配置的命令。 在網絡故障排除、修復網絡連接問題以及清除可能存在的網絡配置沖突時非常有用。 命令詳解&#xff1a; netsh: 用于配置各種網絡設置 int: 用于管理網絡接口 ip: 用于管理網絡接口的 IP 配…

layui項目中的layui.define、layui.config以及layui.use的使用

第一步:創建一個layuiTest項目&#xff0c;結構如下 第二步&#xff1a;新建一個test.js,利用layui.define定義一個模塊test,并向外暴露該模塊&#xff0c;該模塊里面有兩個方法method1和method2. 第三步&#xff1a;新建一個test.html&#xff0c;在該頁面引入layui.js&#x…

基于FPGA的LDPC編譯碼算法設計基礎知識

基于FPGA的LDPC編譯碼算法設計基礎知識 數字電路&#xff08;數電&#xff09;知識模擬電路&#xff08;模電&#xff09;知識1. 放大器1.1. 晶體管放大器1.2. 運算放大器1.3. 管子放大器&#xff08;真空管放大器&#xff09;微處理器/單片機知識其他相關知識 基于FPGA的算法設…

neo4j 圖數據庫:Cypher 查詢語言、醫學知識圖譜

neo4j 圖數據庫&#xff1a;Cypher 查詢語言、醫學知識圖譜 Cypher 查詢語言創建數據查詢數據查詢并返回所有節點查詢并返回所有帶有特定標簽的節點查詢特定屬性的節點及其所有關系和關系的另一端節點查詢從名為“小明”的節點到名為“小紅”的節點的路徑 更新數據更新一個節點…

python爬蟲和用騰訊云API接口進行翻譯并存入excel,通過本機的Windows任務計劃程序定時運行Python腳本!

項目場景&#xff1a; 提示&#xff1a;這里簡述項目相關背景&#xff1a;定時爬取外網的某個頁面&#xff0c;并將需要的部分翻譯為中文存入excel 接下了的&#xff0c;沒學過的最好看一下 基本爬蟲的學習 【爬蟲】requests 結合 BeautifulSoup抓取網頁數據_requests beauti…

Vue CoreVideoPlayer 一款基于 vue.js 的輕量級、優秀的視頻播放器組件

大家好,我是程序視點的小二哥!今天小二哥給大家推薦一款非常優秀的視頻播放組件 效果欣賞 介紹 Vue-CoreVideoPlayer 一款基于vue.js的輕量級的視頻播放器插件。 采用Adobd XD進行UI設計&#xff0c;支持移動端適配,不僅功能強大&#xff0c;顏值也是超一流&#xff01; Vue-…

第一次構建一個對話機器人流程解析(二)

1. 問答機器人的組成-基于知識圖譜的搜索 在教育場景下&#xff0c;若學生有關于學習內容的提問&#xff0c;或業務層面的提問&#xff0c;則要求問答機器人的回答必須精準&#xff0c;來滿足業務的要求因此需要通過知識圖譜來快速檢索&#xff0c;所提內容的相關信息&#xf…

數字系統與進制轉換

數字系統 數字邏輯是計算機科學的基礎&#xff0c;它研究的是如何通過邏輯門電路&#xff08;與門、或門、非門等&#xff09;實現各種邏輯功能。數字系統則是由數字邏輯電路組成的系統&#xff0c;可以實現各種復雜的運算和控制功能。在計算機科學中&#xff0c;數字邏輯和數…

C++ 假設今天是星期日,那么過a^b天之后是星期幾?

題目 假設今天是星期日&#xff0c;那么過a^b天之后是星期幾&#xff1f; 【輸入】 兩個正整數a&#xff0c;b&#xff0c;中間用單個空格隔開。0<a≤100,0<b≤10000。 【輸出】 一個字符串&#xff0c;代表過a^b天之后是星期幾。 其中&#xff0c;Monday是星期一&…

自定義波形圖View,LayoutInflater動態加載控件保存為本地圖片

效果圖: 頁面布局: <?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="…

C#多線程并行計算實例

在C#中實現多線程并行計算可以通過使用 Task 和 Parallel 類來實現。這里給出兩個簡單的示例&#xff0c;一個是使用 Task&#xff0c;另一個是使用 Parallel.ForEach。 使用 Task 進行多線程并行計算 using System; using System.Threading.Tasks;class Program {static voi…

Kubernetes基于helm部署jenkins

Kubernetes基于helm安裝jenkins jenkins支持war包、docker鏡像、系統安裝包、helm安裝等。在Kubernetes上使用Helm安裝Jenkins可以簡化安裝和管理Jenkins的過程。同時借助Kubernetes&#xff0c;jenkins可以實現工作節點的動態調用伸縮&#xff0c;更好的提高資源利用率。通過…

MySQL Innodb存儲引擎中,當頁默認的大小是16K時,頁中最多存放多少行的記錄?

1、題目引入 Innodb存儲引擎是面向行的(row-oriented)&#xff0c;也就是說數據的存放按行進行&#xff0c;每頁存放的行記錄是有硬性定義的&#xff0c;當頁默認的大小是16K時&#xff0c;頁中最多存放多少行的記錄&#xff1f; A、1600 行B、8192 行C、16383 行D、7992 行 …

基于Python協同過濾的旅游景點推薦系統,采用Django框架,MySQL數據存儲,Bootstrap前端,echarts可視化實現

隨著旅游業的迅速發展&#xff0c;個性化旅游推薦系統成為提升用戶體驗和促進旅游市場增長的重要工具。本研究旨在設計并實現一種基于Python協同過濾的旅游景點推薦系統&#xff0c;結合Django框架、MySQL數據庫存儲、Bootstrap前端框架以及echarts數據可視化技術&#xff0c;為…