Spring MVC深入理解之源码实现

news2024/10/1 21:13:11

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 {
    @RequestMapping
    public  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 {
    @RequestMapping
    public  void index(){
        System.out.println("index -> index");
    }
}

最终执行结果如下:

index -> index
test->index1
test->index
没有这个方法 404
test->index

以上就是简单实现SpringMVC的全部源码,如有错误,欢迎指正!!! 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1912558.html

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

相关文章

CLion学习笔记-cmake编译和多main函数编译

这里就不讲怎么配置clion了 项目名字 pcl_kdtree_search 1.新建一个工程名字自己取&#xff0c;我这里用自己学习pcl的&#xff0c;加一个main函数&#xff0c;这个时候Cmake里边就是这样的。 #声明要求的cmake最低版本 cmake_minimum_required(VERSION 3.19) #声明一个工程…

【每日一练】python基础入门实例

""" 幼儿园加法练习题 题数不限 每满100分奖励10个棒棒糖 要求&#xff1a; 1.使用三目运算符与基础运算的对比 2.随机数字相加 3.调用函数 4.循环执行练习题 5.有计算分数 6.有时间停止休眠 """ #导入随机模块 import random #导入时间模块 imp…

华为乾崑智驾加持:深蓝S07首次亮相

最近&#xff0c;特斯拉FSD即将入华的消息&#xff0c;让智能驾驶成为了汽车行业热议的焦点&#xff0c;而当新能源汽车的代表企业深蓝汽车&#xff0c;与全球领先的华为乾崑智驾强强联手&#xff0c;一场颠覆性的智能出行变革也已蓄势待发。 7月8日&#xff0c;深蓝汽车携其最…

uniapp自动升级

一、创建云服务空间&#xff08;https://unicloud.dcloud.net.cn&#xff09; 云空间用于关联需要版本控制升级的项目&#xff0c;如果已拥有云空间则省略此步骤。 二、搭建 uni升级中心 - 后台管理系统&#xff08;升级中心 uni-upgrade-center - Admin&#xff09; uni-adm…

DDR3 SO-DIMM 内存条硬件总结(一)

最近在使用fpga读写DDR3&#xff0c;板子上的DDR3有两种形式与fpga相连&#xff0c;一种是直接用ddr3内存颗粒&#xff0c;另一种是通过内存条的形式与fpga相连。这里我们正好记录下和ddr3相关的知识&#xff0c;先从DDR3 SO-DIMM 内存条开始。 1.先看内存条的版本 从JEDEC下载…

Elasticsearch:深度学习与机器学习:了解差异

作者&#xff1a;来自 Elastic Elastic Platform Team 近年来&#xff0c;两项突破性技术一直站在创新的最前沿 —— 机器学习 (machine learning - ML) 和深度学习 (deep learning - DL)。人工智能 (AI) 的这些子集远不止是流行语。它们是推动医疗保健、金融等各行业进步的关键…

vue3 antdv Modal通过设置内容里的容器的最小高度,让Modal能够适当的变高一些

1、当收款信息Collapse也折叠的时候&#xff0c;我们会发现Modal的高度也变成了很小。 2、我们希望高度稍微要高一些&#xff0c;这样感觉上面显示的Modal高度太小了&#xff0c;显示下面的效果。 3、初始的时候&#xff0c;想通过class或者style或者wrapClassName来实现&#…

理解局域网技术:从基础到进阶

局域网&#xff08;LAN&#xff09;是在20世纪70年代末发展起来的&#xff0c;起初主要用于连接单位内部的计算机&#xff0c;使它们能够方便地共享各种硬件、软件和数据资源。局域网的主要特点是网络为一个单位所拥有&#xff0c;地理范围和站点数目均有限。 局域网技术在计算…

【排序算法】快速排序(详解+各版本实现)

目录 一.交换排序 1.基本思想 2.冒泡排序 二.快速排序 1.hoare版本 2.挖坑法 3.前后指针版本 4.优化 优化①&#xff1a;三数取中 优化②&#xff1a;小区间优化 5.非递归版本 6.特性总结 ①效率 ②时间复杂度&#xff1a;O(N*logN) ③空间复杂度&#xff1a;O(l…

拓展神经网络八股(入门级)

自制数据集 minst等数据集是别人打包好的&#xff0c;如果是本领域的数据集。自制数据集。 替换 把图片路径和标签文件输入到函数里&#xff0c;并返回输入特征和标签 只需要把图片灰度值数据拼接到特征列表&#xff0c;标签添加到标签列表,提取操作函数如下&#xff1a; def…

STM32快速搭建项目框架

注&#xff1a;编写本博客的原因&#xff0c;学习期间基于复习之前知识点的需要&#xff0c;故撰写本教程&#xff0c;即是复习前面的知识点也是作为博客的补充 1.0 文件夹的创建 创建一个STM32项目为模版工程&#xff0c;问价夹下分别包含4个子文件夹&#xff0c;一个是Librar…

【初阶数据结构】1.算法复杂度

文章目录 1.数据结构前言1.1 数据结构1.2 算法1.3 如何学好数据结构和算法 2.算法效率2.1 复杂度的概念2.2 复杂度的重要性 3.时间复杂度3.1 大O的渐进表示法3.2 时间复杂度计算示例3.2.1 示例13.2.2 示例23.2.3 示例33.2.4 示例43.2.5 示例53.2.6 示例63.2.7 示例7 4.空间复杂…

阻尼振动的可视化 包括源码和推导

阻尼振动的可视化 包括源码和推导 flyfish 牛顿第二定律&#xff08;加速度定律&#xff09; 胡克定律&#xff08;Hooke‘s Law&#xff09; 阻尼振动是指在振动系统中&#xff0c;由于阻力或能量损耗导致振动幅度随时间减小的现象。 左边为无阻尼&#xff0c;右边为有阻尼…

【WEB前端2024】3D智体编程:乔布斯3D纪念馆-第57-agent机器人助理自动获取喵星人资讯

【WEB前端2024】3D智体编程&#xff1a;乔布斯3D纪念馆-第57-agent机器人助理自动获取喵星人资讯 使用dtns.network德塔世界&#xff08;开源的智体世界引擎&#xff09;&#xff0c;策划和设计《乔布斯超大型的开源3D纪念馆》的系列教程。dtns.network是一款主要由JavaScript…

FastReport 指定sql 和修改 数据库连接地址的 工具类 :FastReportHelper

FastReport 指定sql 和修改 数据库连接地址的 工具类 &#xff1a;FastReportHelper 介绍核心代码&#xff1a;完整代码&#xff1a; 介绍 在FastReport中&#xff0c;经常会遇到需要给 sql 加条件的情况&#xff0c;或者给数据库地址做更换。 &#xff08;废话不多说&#x…

Elasticsearch基础(四):Elasticsearch语法与案例介绍

文章目录 Elasticsearch语法与案例介绍 一、Restful API 二、查询语法 1、ES分词器 2、ES查询 2.1、match 2.2、match_phrase 2.3、multi_match 2.4、term 2.5、terms 2.6、fuzzy 2.7、range 2.8、bool Elasticsearch语法与案例介绍 一、Restful API Elastics…

Echarts实现github提交记录图

最近改个人博客&#xff0c;看了github的提交记录&#xff0c;是真觉得好看。可以移植到自己的博客上做文章统计 效果如下 代码如下 <!DOCTYPE html> <html lang"en" style"height: 100%"><head><meta charset"utf-8"> …

需求分析|泳道图 ProcessOn教学

文章目录 1.为什么使用泳道图2.具体例子一、如何绘制确定好泳道中枢的角色在中央基于事实来绘制过程不要纠结美观先画主干处理流程再画分支处理流程一个图表达不完&#xff0c;切分子流程过程数不超25 &#xff0c;A4纸的幅面处理过程过程用动词短语最后美化并加上序号酌情加上…

未羽研发测试管理平台

突然有一些觉悟&#xff0c;程序猿不能只会吭哧吭哧的低头做事&#xff0c;应该学会怎么去展示自己&#xff0c;怎么去宣传自己&#xff0c;怎么把自己想做的事表述清楚。 于是&#xff0c;这两天一直在整理自己的作品&#xff0c;也为接下来的找工作多做点准备。接下来…

2-29 基于matlab的CEEMD

基于matlab的CEEMD&#xff08;Complementary Ensemble Empirical Mode Decomposition&#xff0c;互补集合经验模态分解&#xff09;&#xff0c;先将数据精心ceemd分解&#xff0c;得到imf分量&#xff0c;然后通过相关系数帅选分量&#xff0c;在求出他们的样本熵的特征。用…