SpringMvc拦截器和手写模拟SpringMvc工作流程源码详解

news2024/11/13 9:00:07

目录

1. SpringMvc简介

1.1 什么是MVC

1.2 什么是SpringMvc

1.3 SpringMvc 能干什么

1.4 SpringMvc 工作流程

2. SpringMvc拦截器和过滤器

2.1 拦截器

2.1.1 拦截器作用

2.1.2 拦截器和过滤器的区别

2.1.3 拦截器方法说明

2.1.4 多个拦截器执行顺序

2.1.5 自定义拦截器

2.2 过滤器(附加)

3. 手写模拟SpringMvc源码

3.1 目录结构如下

3.2 导入依赖

3.3 分析

3.4 测试


1. SpringMvc简介

1.1 什么是MVC

MVC是一种软件架构的思想,将软件按照模型、视图、控制器来划分。
M: Model,模型层,指工程中的JavaBean,作用是处理数据。
JavaBean分为两类:
1.实体类Bean:专门存储业务数据的,如Student User等
2.业务处理Bean:指Service或Dao对象,专门用于处理业务逻辑和数据访问。
V: View,视图层,指工程中的html或jsp等页面,作用是与用户进行交互,展示数据。
C: Controller,控制层,指工程中的servlet,作用是接收请求和响应浏览器。
MVC的工作流程:
用户通过视图层发送请求到服务器,在服务器中请求被Controller接收,Controller调用相应的Model层处理请求,处理完毕将结果返回到Controller,Controller再根据请求处理的结果找到相应的View视图,渲染数据后最终响应给浏览器。

1.2 什么是SpringMvc

SpringMVC是Spring的一个后续产品,是Spring的一个子项目。
SpringMVC是Spring为表述层开发提供的一整套完备的解决方案。在表述层框架历经Strust、WebWork,Strust2等诸多产品的历代更迭之后,目前业界普遍选择了SpringMVC作为JavaEE项目表述层开发的首选方案。

SpringMVC是一种基于Java的实现了Web MVC设计模式的请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将web层进行职责解耦,帮助我们简化开发。

1.3 SpringMvc 能干什么

     1)天生与Spring框架集成,如:(IOC,AOP)

  2)支持Restful风格

  3)进行更简洁的Web层开发

  4)支持灵活的URL到页面控制器的映射

  5)非常容易与其他视图技术集成,如:Velocity、FreeMarker等等

  6)因为模型数据不存放在特定的API里,而是放在一个Model里(Map数据结构实现,因此很容易被其他框架使用)

  7)非常灵活的数据验证、格式化和数据绑定机制、能使用任何对象进行数据绑定,不必实现特定框架的API

  8)更加简单、强大的异常处理

  9)对静态资源的支持

     10)支持灵活的本地化、主题等解析

1.4 SpringMvc 工作流程

SpringMvc工作流程如下图:

具体步骤:

  • 第一步:发起请求到前端控制器DispatcherServlet。
  • 第二步:前端控制器DispatcherServlet收到请求后调用处理器映射器HandlerMapping。
  • 第三步:处理器映射器HandlerMapping 根据请求的URL找到具体的处理器,生成处理器对象Handler 以及处理器拦截器HandlerIntercepter(如果有则生成),并返回给向前端控制器。
  • 第四步:前端控制器DispatcherServlet通过处理器适配器HandlerAdapter去调用处理器Controller。
  • 第五步:调用处理器(Controller,也叫控制器)。
  • 第六步:处理器Controller执行完成给适配器返回ModelAndView
  • 第七步:处理器适配器HandleAdapter将处理器Controller返回的结果ModelAndView返回给前端控制器DispatcherServlet。
  • 第八步:前端控制器DispatcherServlet将ModelAndView传给视图解析器ViewResolver。
  • 第九步:视图解析器ViewResolver解析后向前端控制器DispatcherServlet返回View。
  • 第十步:前端控制器DispatcherServlet进行视图渲染 (视图渲染将模型数据(在ModelAndView对象中)填充到request域)。
  • 第十一步:前端控制器DispatcherServlet向用户响应结果。

2. SpringMvc拦截器和过滤器

2.1 拦截器

2.1.1 拦截器作用

SpringMVC的拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理
将拦截器按一定的顺序连接成链,这条链称为拦截器链(Interceptor chain)。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。拦截器也是AOP思想的具体实现。

2.1.2 拦截器和过滤器的区别

如下图:

2.1.3 拦截器方法说明

 Spring MVC也可以使用拦截器对请求进行拦截处理,用户可以自定义拦截器来实现特定的功能,自定义的拦截器可以实现HandlerInterceptor接口,也可以继承HandlerInterceptorAdapter 适配器类 。

① preHandle():这个方法在业务处理器处理请求之前被调用,在该方法中对用户请求 request 进行处理。如果程序员决定该拦截器对请求进行拦截处理后还要调用其他的拦截器,或者是业务处理器去进行处理,则返回true;如果程序员决定不需要再调用其他的组件去处理请求,则返回false。

② postHandle():这个方法在业务处理器处理完请求后,但是DispatcherServlet 向客户端返回响应前被调用,在该方法中对用户请求request进行处理。

③ afterCompletion():这个方法在 DispatcherServlet 完全处理完请求后被调用,可以在该方法中进行一些资源清理的操作。

2.1.4 多个拦截器执行顺序

2.1.5 自定义拦截器

自定义拦截器步骤如下:

①创建拦截器类实现HandlerInterceptor接口

②配置拦截器

③测试拦截器的拦截效果

这里采用Springboot框架,代码如下:

创建拦截器类实现HandlerInterceptor接口

public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader("token");
        System.out.println("执行了preHandle方法");
        if(token.equals("admin"))
            return true;
        else
            return false;

    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("执行了postHandle方法");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("执行了afterCompletion方法");
    }
}

配置拦截器

@Configuration
public class MyConfiguration implements WebMvcConfigurer {


    @Bean
    public LoginInterceptor loginInterceptor(){
        return new LoginInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/test1");
    }
}

2.2 过滤器(附加)

过滤器是Web开发中很实用的一项技术, 开发人员可以通过过滤器对Web服务管理的资源静态HTML文件、静态图片、JSP、 Servlet 等进行拦截,从而实现一一 些特殊的需求,比如设置URL的访问权限、过滤敏感词汇、压缩响应信息等。过滤器还适用于对用户请求和响应对象进行检查和修改,但是Filter本身并不生成请求和响应对象,只是提供过滤功能。Filter 的完整工作流程如图所示:

当客户瑞发出对Web资源的请求时,Web服务器会根据应用程序配置文件设置的过滤规则进行检查,若客户端请求满足过滤规则,则对客户端请求响应进行拦截。首先按照需求对请求头和请求数据进行封装,并依次通过过滤器链,然后把请求/响应交给Web资源处理,请求信息在过滤器链中可以被修改,也可以根据条件让请求不发往资源处理器,并直接向客户机发回一个响应。当资源处理器完成了对资源的处理后,响应信息将逐级逆向返回。在这个过程中,用户可以修改响应信息,从而完成一定的任务。 这就是过滤器的工作原理。

另外,过滤器的生命周期也是由Web服务器进行负责的,但是相比真正的Servlet又有区别。Filter 的生命周期大致分为以下三个阶段:

(1)实例化: Web容器在部署Web应用程序时对所有过滤器进行实例化,此时Web容器调用的是它的无参构造方法。

(2)初始化:实例化完成之后,马上进行初始化工作。Web容器回调initO方法。请求路径匹配过滤器的URL映射时,Web容器回调过滤器的doFilter()方法,此方法也是过滤器的核心方法。

3)销毁: Web容器在卸载Web应用程序前回调doDestory 方法。 

在Springboot中要在启动类上加上@ServletComponentScan注解

过滤器代码如下:

@WebFilter("/*")
public class WebTestFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("过滤器前进");
        chain.doFilter(request,response);
        System.out.println("过滤器返回");
    }

    @Override
    public void destroy() {

    }
}

注意:在Controller业务处理中,如果有请求的转发,拦截器会拦截多次,而过滤器并不会。

3. 手写模拟SpringMvc源码

3.1 目录结构如下

3.2 导入依赖

 <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
    </dependency>
    <!--       解析xml文件-->
    <dependency>
      <groupId>dom4j</groupId>
      <artifactId>dom4j</artifactId>
      <version>1.6.1</version>
    </dependency>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.9</version>
    </dependency>

3.3 分析

在启动Tomcat后,会自动解析webapp中的WEB-INF中的web.xml, 所以我们在web.xml文件配置如下:

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Application</display-name>

  <servlet>
    <servlet-name>DispatcherServlet</servlet-name>
    <servlet-class>com.example.demo.springmvc.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>DispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

这里的DispatcherServlet为我们自己创建的类。当Tomcat启动后,就会解析Web.xml文件,并且创建我们自定义的DispatcherServlet类。在上面目录中我们创建了ApplicationContext类,这里相当于Spring容器,这块的代码属于Spring源码部分,在这里省略。然后就会执行DispatcherServlet类中的init()方法,该方法作用为创建Spring容器,从Springmvc.xml文件中读取base-package中的包路径,这里Spring容器会扫描这里的包路径并且生成Controller类型的Bean对象,init方法代码如下:

private ApplicationContext applicationContext; //Spring容器
 

    @Override
    public void init() throws ServletException {
        String contextConfigLocation = this.getServletConfig().getInitParameter("contextConfigLocation"); //这里获取Web.xml文件中的contextConfigLocation参数,这里为classpath:springmvc.xml。
        applicationContext = new ApplicationContext(contextConfigLocation);//这里为创建Spring容器,从Springmvc.xml文件中读取base-package中的包路径,这里Spring容器会扫描这里的包路径并且生成Controller类型的Bean.
        applicationContext.refresh();
        initHandleMappinng(applicationContext);

    }

Spring容器将Spring.xml文件中的包路径下的Controller生成Bean对象后,执行DispatcherServlet中的initHandleMappinng方法,该方法会遍历Spring容器中beanDefinitionConcurrentHashMap,遍历其中的所有的Controller类型的Bean对象的Class对象,然后判断每一个Class对象中的所有方法,将有@RequestMapping注解的方法,进行封装成一个MyHandle对象,其中包含@RequestMapping注解的Value值,该方法名,Class对象等,然后将这个MyHandle对象放到集合中。代码如下:

  public void initHandleMappinng(ApplicationContext applicationContext){
        if(applicationContext.beanDefinitionConcurrentHashMap.size()==0){
            throw new RuntimeException("Spring容器为空");
        }
        for (Map.Entry<String, BeanDefinition> stringBeanDefinitionEntry : applicationContext.beanDefinitionConcurrentHashMap.entrySet()) {
            Class clazz = stringBeanDefinitionEntry.getValue().getClazz();
            for (Method declaredMethod : clazz.getDeclaredMethods()) {
                boolean annotationPresent = declaredMethod.isAnnotationPresent(RequestMapping.class);
                if(annotationPresent==true){
                    String value = declaredMethod.getAnnotation(RequestMapping.class).value();
                    MyHandle myHandle=new MyHandle(value,declaredMethod,clazz);
                    myHandleList.add(myHandle);
                }
            }
        }
    }

当有get请求的时候就会执行DispatcherServlet中的doGet方法,然后我们的业务逻辑如下:在这里我们会遍历我们存放MyHandle对象的集合中的元素,寻找浏览器url请求路径和我们MyHandle对象中储存的@RequestMapping中相等的对象,然后设置个Object数组,经过一系列的关于@RequestParam参数的判断,将浏览器请求路径中的参数放到对应位置的Object数组中,然后通过method.invoke执行这个方法就可以执行我们的方法并获得返回值,然后我们就可以通过PrintWriter writer = response.getWriter(); writer.print(); 将返回的数据打印到浏览器上。代码如下:

public void excuteDispatch(HttpServletRequest request,HttpServletResponse response){
        MyHandle handle = getHandle(request);
        if(handle==null){
            try {
                response.getWriter().print("404");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        else {
            Method method = handle.getMethod();
            Class<?>[] parameterTypes = method.getParameterTypes();
            Object[] params=new Object[parameterTypes.length];
            Map<String, String[]> parameterMap = request.getParameterMap();
            for (Map.Entry<String, String[]> stringEntry : parameterMap.entrySet()) {
                String key = stringEntry.getKey();
                String value = stringEntry.getValue()[0];
                int i = GetRequestParams(method, key);
                if(i>=0)
                    params[i]=value;
                else {
                    //反射获取的是arg0,官方这里用的不是反射机制
                }
            }
            try {
                Object invoke = method.invoke(handle.getClazz().newInstance(), params);
                PrintWriter writer = response.getWriter();
                writer.print(invoke);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }

其余的代码省略,全部代码可以下载我的文件资源进行查看。

3.4 测试

我们自己模拟写了一个简单的SpringMvc框架,启动Tomcat后然后我们进行验证如下:

测试代码如下:

@Controller("mycontroller")
public class MyController {

    @RequestMapping("/test")
    public String test(@RequestParam("name") String name, HttpServletResponse response){
        return name;
    }
}

运行结果如下图:

 由此可见我们的测试结果非常完美。

这篇文章也结束了,SpringMvc源码模拟可以在我的文件资源下载,链接为:

https://download.csdn.net/download/qq_43649937/87558006

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

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

相关文章

29 虚拟地址到物理地址的转换

前言 呵呵 这是 linux 中内存管理中很基础的一环 用户程序 操作的地址都是虚拟地址, 虚拟地址通过 mmu 转换为物理地址 用户程序 看到的地址都是一个完整的世界, 只有具体需要使用的时候 产生缺页中断, 然后 分配具体的物理页 这里 要说的就是 虚拟地址 到 物理地址 的转…

1001router6-react

文章目录 1 一级路由2 Navigate3 NavLink 自定义高亮样式4 useRoutes()5 嵌套路由6 路由传参6.1 传递params参数6.2 传递search参数6.3 传递state参数 7 编程式导航7.1 路由跳转7.2 前进、后退 8 钩子函数8.1 useInRouterContext()8.2 useNavigationType()8.3 useOutlet()8.4 u…

手写Spring框架---AOP实现

目录 容器是OOP的高级工具 系统需求 关注点分离Concern Separation 原有实现 AOP的成员 Advice的种类 单个Aspect的执行顺序 多个Aspect的执行顺序 Introduction-引入型Advice 代理模式 JDK动态代理 Spring AOP的实现原理之JDK动态代理 Spring AOP的实现原理之CGL…

CSS基础学习--19 下拉菜单

一、基本下拉菜单 当鼠标移动到指定元素上时&#xff0c;会出现下拉菜单 <!DOCTYPE html> <html> <head> <title>下拉菜单实例</title> <meta charset"utf-8"> <style> .dropdown {position: relative;display: inline-…

UnityVR-项目的管理阶层

目录 概述 项目的总体架构 单例基类 继承MonoBehaviour的单例基类 概述 一个具备一定规模的项目&#xff0c;一般都需要由不同人员合作完成&#xff0c;每个人的想法风格不相同&#xff0c;如果一开始没有定下基本的框架&#xff0c;会让实现时混乱不堪&#xff0c;而且无法…

【CesiumJS入门】(5)GooJSON的加载、更新、监听与销毁——GeoJsonDataSource应用

前言 本篇&#xff0c;我们将较完整得介绍Cesium中GeoJSON/TopoJSON相关的方法。 GeoJSON规范地址&#xff1a;RFC 7946: The GeoJSON Format (rfc-editor.org) GeoJSON在线绘制&#xff1a;geojson.io CesiumJS提供了一个名为DataSource的类&#xff0c;它主要是用来加载和展…

Java-API简析_java.util.UUID类(基于 Latest JDK)(浅析源码)

【版权声明】未经博主同意&#xff0c;谢绝转载&#xff01;&#xff08;请尊重原创&#xff0c;博主保留追究权&#xff09; https://blog.csdn.net/m0_69908381/article/details/131270140 出自【进步*于辰的博客】 其实我的【Java-API】专栏内的博文对大家来说意义是不大的。…

《计算之魂》读书笔记——递归与堆栈的关系

进入梅雨季节&#xff0c;一周末雨水连绵不绝&#xff0c;空气中泛着潮湿的凉爽。这个天气最适合找个角落&#xff0c;安安静静地读书写字。 继续读《计算之魂》&#xff0c;前次读到递归&#xff0c;今天则了解递归地数据结构实现。递归算法的层层实现&#xff0c;需要保留从…

大数据周会-本周学习内容总结018

开会时间&#xff1a;2023.06.18 15:00 线下会议 01【调研-数据分析&#xff08;质量、ETL、可视化&#xff09;】 ETL&#xff0c;是英文Extract-Transform-Load的缩写&#xff0c;用来描述将数据从来源端经过抽取&#xff08;extract&#xff09;、转换&#xff08;transform…

Tcl常用命令备忘录-正则命令篇

正则表达式是一种用于匹配、查找、替换文本中特定模式的工具。在Tcl脚本中&#xff0c;可以使用正则表达式对字符串进行匹配、查找和替换。 regexp 语法&#xff1a; regexp ?选项? 正则表达式 字符串 ?变量1 变量2 ...? 其中&#xff0c;?选项?为可选项&#xff0c;…

基于蒙特卡洛法的规模化电动汽车充电负荷预测(PythonMatlab实现)

目录 0 概述 1 蒙特卡洛模拟方法介绍 2 规模化电动汽车充电负荷预测计算方法 3 完整代码 0 概述 对于本文的研究,依据不同用途电动汽车影响因素的分布函数和设定参数&#xff0c;采用蒙特卡洛法,对各用途电动汽车的日行驶里程、起始充电时间概率分布参数进行随机抽样&#xff0…

linuxOPS系统服务_Linux下软件的安装方式之源码安装

Linux下有哪些软件安装方式 ① RPM软件包管理&#xff08;软件名称.rpm&#xff09; ② YUM软件包管理&#xff08;使用yum命令install 软件名称&#xff09; > 下载 安装一体化 ③ 源码编译安装&#xff08;相对来说是最复杂的一种方式&#xff09; 软件包类型 ☆ 二…

十二、docker学习-docker核心docker网络之bridge网络(2)

bridge网络 bridge网络表现形式就是docker0这个网络接口。容器默认都是通过docker0这个接口进行通信。也可以通过docker0去和本机的以太网接口连接&#xff0c;这样容器内部才能访问互联网。 # 查看docker0网络&#xff0c;在默认环境中&#xff0c;一个名为docker0的linux b…

Go语言的TCP和HTTP网络服务基础

目录 【TCP Socket 编程模型】 Socket读操作 【HTTP网络服务】 HTTP客户端 HTTP服务端 TCP/IP 网络模型实现了两种传输层协议&#xff1a;TCP 和 UDP&#xff0c;其中TCP 是面向连接的流协议&#xff0c;为通信的两端提供稳定可靠的数据传输服务&#xff1b;UDP 提供了一种…

NodeJSMongodbMVC管理开发⑨

文章目录 ✨文章有误请指正&#xff0c;如果觉得对你有用&#xff0c;请点三连一波&#xff0c;蟹蟹支持&#x1f618;前言MVC思想开发 服务器代码演示 M层 Services 或 Model V层 Views C层 Controllers总结 ✨文章有误请指正&#xff0c;如果觉得对你有用&#xff0c…

C语言笔记之结构体总结

C语言笔记之结构体总结 code review! 文章目录 C语言笔记之结构体总结一.介绍二.3种结构体类型变量说明1. 先定义结构&#xff0c;再定义结构变量2. 定义结构体类型的同时说明变量3. 直接说明结构变量(匿名结构体) 四.结构体成员表示方法五.结构体指针做参数六.结构体初始化1…

阵列信号处理笔记(2):均匀线阵、均匀加权线阵、波束方向图

阵列信号处理笔记&#xff08;2&#xff09; 文章目录 阵列信号处理笔记&#xff08;2&#xff09;均匀线阵&#xff08;Uniform Linear Array&#xff09;均匀加权线阵波束方向图的关键参数附polardb.m用来计算HPBW的Mathematica代码&#xff0c;以及用于拟合的数据拟合的MATL…

二、DSMP/OLS等夜间灯光数据贫困地区识别——MPI和灯光指数计算

一、前言 其实在计算MPI和灯光指数之前,最重要是DMSP/OLS等夜间灯光指数的校正还有就是MPI计算,那么校正分为DMSP/OLS和NPP/VIIRS夜间灯光数据,DMSP/OLS夜间灯光数据校正主要采取不变目标区域法原理进行校正,当前对其有很多优化后的做法,但是万变不离其宗,核心思想还是没…

LeetCode257. 二叉树的所有路径

写在前面&#xff1a; 题目链接&#xff1a;LeetCode257. 二叉树的所有路径 题目难度&#xff1a;简单 编程语言&#xff1a;C 一、题目描述 给你一个二叉树的根节点 root &#xff0c;按 任意顺序 &#xff0c;返回所有从根节点到叶子节点的路径。 叶子节点 是指没有子节点的…

阿里P8架构师手码的Java工程师面试小抄在Github火了,完整版限时开源

网上的 JAVA 面试文档更是层出不穷。但是单单刷 JAVA 面试题就足够了吗&#xff1f; 答案显然是不够的&#xff01;那么为什么呢&#xff1f; 因为现在的程序员就业环境早就和两年前不可同日而语了。 如果你在两年前面试&#xff1a; 就拿 JVM 来说&#xff0c;刷面试题可能…