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.mzph.cn/web/42342.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

单链表详解(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;查…

【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…

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…

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;数字逻辑和数…

自定义波形图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="…

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;为…

【粉丝福利 | 第8期】值得收藏!推荐10个好用的数据血缘工具

⛳️ 写在前面参与规则&#xff01;&#xff01;&#xff01; ✅参与方式&#xff1a;关注博主、点赞、收藏、评论&#xff0c;任意评论&#xff08;每人最多评论三次&#xff09; ⛳️本次送书1~4本【取决于阅读量&#xff0c;阅读量越多&#xff0c;送的越多】 目前市面上绝…

数据迁移探索

概念 数据迁移是指将数据从一个计算环境或存储系统移动到另一个计算环境或存储系统。 随着公司业务的发展&#xff0c;出于成本优化、系统升级、分库分表、整合数据等原因。数据迁移工作在日常工作中会陆续出现。 我们可以将数据迁移分成两个部分&#xff0c;第一部分是数据…

springboot高职院校毕业生信息管理系统-计算机毕业设计源码27889

摘 要 基于Java语言开发的高职院校毕业生信息管理系统旨在提供一个便捷、高效的方式来管理毕业生的相关信息。系统包括学生基本信息管理、成绩管理、就业信息管理等模块&#xff0c;通过界面友好、操作简单的设计&#xff0c;方便管理员快速查询和更新学生信息。系统还提供数据…

采用前后端分离技术架构+java语言开发的全套产科信息管理系统源码 可与医院HIS、LIS、PACS、RIS等系统进行对接

采用前后端分离技术架构java语言开发的全套产科信息管理系统源码 可与医院HIS、LIS、PACS、RIS等系统进行对接 什么是产科信息管理系统-建档管理&#xff1f; 产科信息管理系统建档管理通过信息技术实现了孕产妇健康信息的电子化、网络化和智能化管理&#xff0c;提高了医疗服…

Riscv 架构的合规测试

为啥直接关注riscv-arch-test&#xff0c;是因为RISCOF 测试框架使用的是riscv-arch-test 1. The architectural test 架构测试是一个单一的测试&#xff0c;代表了可编译和运行的最小测试代码。它是用汇编代码编写的&#xff0c;其产品是test signature。一个架构测试可能由…

微信小程序推送消息java版

需求背景 使用springboot2微信小程序推送消息。百度了一下看了一篇文章整体还可以&#xff0c;这里推荐一下。 教程解析 1、微信平台开通订阅消息。 2、添加模板。 3、springboot后台接口开发 微信平台验证地址接口开发&#xff0c;然后部署到公网服务器。 4、微信平台验…

基于FPGA的数字信号处理(15)--定点数的舍入模式(6)向0取整fix

前言 在之前的文章介绍了定点数为什么需要舍入和几种常见的舍入模式。今天我们再来看看另外一种舍入模式&#xff1a;向上取整fix。 10进制数的fix fix&#xff1a;也叫 向0取整。它的舍入方式是数据往0的方向&#xff0c;舍入到最近的整数&#xff0c;比如1.75 fix到2&#xf…

将WordPress的文章重新排序的3个方法

有效的调整文章显示顺序看开可以更好突出内容&#xff0c;还可以保持网站的新鲜感&#xff0c;今天我将带您了解三种方法&#xff0c;通过重新排序文章显著提升网站的吸引力。我们将逐步讲解从调整设置到使用插件以及“置顶”文章的每一种方法&#xff0c;确保WordPress 新手也…