技术博客 技术博客
  • JAVA
  • 仓颉
  • 设计模式
  • 人工智能
  • Spring
  • Mybatis
  • Maven
  • Git
  • Kafka
  • RabbitMQ
  • RocketMQ
  • Redis
  • Zookeeper
  • Nginx
  • 数据库套件
  • MySQL
  • Elasticsearch
  • MongoDB
  • Hadoop
  • ClickHouse
  • Hbase
  • Hive
  • Flink
  • Flume
  • SQLite
  • linux
  • Docker
  • Jenkins
  • Kubernetes
  • 工具
  • 前端
  • AI
GitHub (opens new window)
  • JAVA
  • 仓颉
  • 设计模式
  • 人工智能
  • Spring
  • Mybatis
  • Maven
  • Git
  • Kafka
  • RabbitMQ
  • RocketMQ
  • Redis
  • Zookeeper
  • Nginx
  • 数据库套件
  • MySQL
  • Elasticsearch
  • MongoDB
  • Hadoop
  • ClickHouse
  • Hbase
  • Hive
  • Flink
  • Flume
  • SQLite
  • linux
  • Docker
  • Jenkins
  • Kubernetes
  • 工具
  • 前端
  • AI
GitHub (opens new window)
  • Spring

    • spring

      • 核心内容拆解 IOC
      • 核心内容拆解 AOP
      • 核心内容拆解 事件通知
      • 核心内容拆解 三级缓存
      • 核心内容拆解 FactoryBean
      • 注解替代Spring生命周期实现类
    • spring mv

      • Spring MVC 之基本工作原理
        • 搭建
        • DispatcherServlet 初始化讲解
        • 父子容器
        • 代码取代xml配置
        • 请求实现
    • spring boot

      • SpringBoot 之 Filter、Interceptor、Aspect
      • SpringBoot 之 Starter
      • SpringBoot 之 Stomp 使用和 vue 相配置
      • SpringBoot MyBatisPlus 实现多数据源
      • SpringBoot MyBatis 动态建表
      • Spring Boot 集成 Jasypt 3.0.3 配置文件加密
      • Spring Boot 集成 FastDFS
      • Spring Boot VUE前后端加解密
      • Spring Boot logback.xml 配置
      • Spring Boot MinIO
      • Spring Boot kafka
      • Spring Boot WebSocket
    • spring cloud

      • SpringCloud - Ribbon和Feign
      • SpringCloud alibaba - Nacos
      • SpringCloud alibaba - Sentinel哨兵
      • SpringCloud alibaba - Gateway
      • SpringCloud alibaba - 链路跟踪
      • SpringCloud - 分布式事务一(XA,2PC,3PC)
      • SpringCloud - 分布式事务二(Seata-AT,TCC,Saga)
      • SpringCloud - 分布式事务三(Seata搭建)
      • SpringCloud - 分布式事务四(多数据源事务)
      • SpringCloud - 分布式事务五(微服务间调用的事务处理)
  • Mybatis

    • 核心功能拆解 工作流程
    • 核心功能拆解 Plugin插件功能实现
    • 核心功能拆解 一二级缓存原理
    • MyBatis Plus+Spring Boot 实现一二级缓存以及自定义缓存
  • maven

    • pom 文件介绍及 parent、properties 标签详解
    • dependencies 标签详解
    • 使用 Nexus3.x 搭建私服
  • git

    • 私有 git 仓库搭建
目录

Spring MVC 之基本工作原理

# 搭建

  1. 配置 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!-- spring 包都有 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.10</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>


    <groupId>com.fengqianrun</groupId>
    <artifactId>study-springMVC</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <!-- tomcat 认war包 -->
    <packaging>war</packaging>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!-- 只使用mvc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
        </dependency>
    </dependencies>

</project>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
  1. 创建 webapp/WEB-INF 目录,并创建 web.xml 文件
<?xml version="1.0" encoding="UTF-8"?>
<!-- 这个文件是tomcat要去读取的文件,文件路径必须在 webapp/WEB-INF 下,webapp和 java是同级目录 -->
<web-app>
    <servlet>
        <servlet-name>app</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <!-- 指定spring.xml地址 -->
            <param-value>/WEB-INF/spring.xml</param-value>
        </init-param>
        <!--数字只是决定初始化顺序
            默认负数:客户端第一次访问才初始化
            大于零:的数表示服务器启动时,初始化
            数字越小越先初始化
        -->
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>app</servlet-name>
        <!-- 访问路径前缀 -->
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>
</web-app>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
  1. 创建 /WEB-INF/spring.xml 文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.fengqianrun.mvc" />

</beans>
1
2
3
4
5
6
7
8
9
  1. 配置 tomcat,Deployment 中配置 war,并修改 Application context
  2. 访问 url
访问地址为 http://ip:port/tomcat配置的Application context/web.xml里的servlet-name/controller/method
1

# DispatcherServlet 初始化讲解

DispatcherServlet 继承自 HttpServletBean,其最终父类还是 Servlet,只是实现了规范,重写了一些方法。 比如 HttpServletBean 该类重写了 init () 方法,在启动指定 DispatcherServlet 的时候,会调用被重写的 init 方法。整体 init 流程如下:

整个 DispatcherServlet 加载流程:
1. tomcat会调用servlet的init()方法
2. HttpServletBean重写了init()方法
    2.1 读取web.xml里面的内容并封装
    2.2 执行核心 initServletBean() 方法
    2.3 initServletBean() 方法调用 initWebApplicationContext()
3. initWebApplicationContext 方法,用于创建 context 上下文
    3.1 调用findWebApplicationContext()查询 web.xml 是否自定义了 contextAttribute 这个属性
    3.2 没用自定义则 createWebApplicationContext(rootContext) 创建 org.springframework.web.context.support.XmlWebApplicationContext 实例,并跟父容器绑定。
    3.3 给 context 设置环境信息 wac.setEnvironment(getEnvironment());
    3.4 设置定义 contextConfigLocation(/WEB-INF/spring.xml) 的路径
    3.5 调用 configureAndRefreshWebApplicationContext() 方法
4. configureAndRefreshWebApplicationContext 方法,配置context上下文,并初始化bean
    4.1 设置上下文 ID,为`应用名称`+`servlet-name`
    4.2 把已有的上下文(cotent)以及配置(config)设置到新的context中
    4.3 给新的context添加一个ApplicationListener,主要为 ContextRefreshListener,当上下文刷新完毕后通知,该类被通知会调用 **FrameworkServlet.this.onApplicationEvent(event);** 方法,这个方法很重要
    4.4 调用 refresh() 方法,执行 bean 的初始化操作(spring那一套),执行完调用 this.finishRefresh(); 也就通知到 4.3 中的 ContextRefreshListener对象并调用 FrameworkServlet.this.onApplicationEvent(event);
5. FrameworkServlet.this.onApplicationEvent(event);调用到DispatcherServlet.initStrategies()方法并会执行以下各种方法: 
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    initHandlerMappings(context); 
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
6. initHandlerMappings,用于把已经加载到spring容器的对象进行挑拣,把实现了 @RequestMapping | @Controller 的Bean摘出来并得到所有实现了@RequestMapping注解的方法 注册到 mappingRegister 容器中
    6.1 从容器中读取到实现了HandlerMapping.class 的Bean,这里就是找我们自定义实现了HandlerMapping.class的Bean
    6.2 如果没有自定义的 HandlerMapping,会加载默认的 HandlerMapping,默认有 BeanNameUrlHandlerMapping,RequestMappingHandlerMapping,RouterFunctionMapping,把找到的注册到 Bean容器中
        6.2.1 RequestMappingHandlerMapping 在注册Bean的时候会执行 afterPropertiesSet()方法,该方法里面会得到所有Bean,并判断类型是否是有 Controller.class 或 RequestMapping.class 注解,如果符合条件代表你是一个 ControllerHandler
        6.2.2 如果是一个 ControllerHandler,则获取该类中的方法并得到有只含有RequestMapping.class注解的方法,并且解析注解上的参数,把方法注册到一个 mappingRegistry 里
    6.3 BeanNameUrlHandlerMapping 是由 ApplicationContextAware 感知调用初始化方法的
        6.3.1 BeanNameUrlHandlerMapping 和 RequestMappingHandlerMapping 解析的方式不一样,BeanNameUrlHandlerMapping得到容器中所有Bean,会判断BeanName的前缀以 '/'开头并收集,并且注册到 handlerMap中
        6.3.2 BeanNameUrlHandlerMapping 和其他controller写法不一样,具体要给类加 @Component("/test") 并且还有实现 implements Controller
        6.3.3 找到匹配条件的方法把他维护到自己的 handlerMap 中
    6.4 注意 BeanNameUrlHandlerMapping 和 RequestMappingHandlerMapping维护了不同的 handler 容器,所以相同的请求路径不会报错,如果相同,执行BeanNameUrlHandlerMapping的方法,因为优先级比RequestMappingHandlerMapping靠前
7. initHandlerAdapters,初始化方法会先把所需要的准备好加载进去
    7.1 initHandlerAdapters 从spring容器中找加了 @ControllerAdvice 的Bean
    7.2 得到Bean后判断加了 @ModelAttribute 的注解但不包含有 @RequestMapping注解的方法 存到 modelAttributeAdviceCache中
    7.3 得到Bean后判断加了 @InitBinder 的方法 存到 initBinderAdviceCache 中
    7.4 从容其中得到所有实现 RequestBodyAdvice 或 ResponseBodyAdvice 接口,记录下来
    7.5 初始化 HttpRequestHandlerAdapter、SimpleControllerHandlerAdapter、RequestMappingHandlerAdapter、HandlerFunctionAdapter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

# 父子容器

父子容器,就是在一个 web.xml 里面指定两个 servlet,加载不同的 spring.xml,共享一个 listener 父容器的对象。注意:父容器是会在 servlet 节点之前解析的。父子容器具体实现如下:

<?xml version="1.0" encoding="UTF-8"?>
<!-- 这个文件是tomcat要去读取的文件,文件路径必须在 webapp/WEB-INF 下,webapp和 java是同级目录 -->
<web-app>
    
    <!-- 父容器 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <!-- 描述bean的文件 -->
        <param-value>/WEB-INF/spring2.xml</param-value>
    </context-param>

    <!-- 子1 -->
    <servlet>
        <servlet-name>app</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <!-- 指定spring.xml地址 -->
            <param-value>/WEB-INF/spring.xml</param-value>
        </init-param>
        <!--数字只是决定初始化顺序
            默认负数:客户端第一次访问才初始化
            大于零:的数表示服务器启动时,初始化
            数字越小越先初始化
        -->
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>app</servlet-name>
        <!-- 访问路径前缀 -->
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>

    <!-- 子2 -->
    <servlet>
        <servlet-name>app1</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <!-- 指定spring.xml地址 -->
            <param-value>/WEB-INF/spring1.xml</param-value>
        </init-param>
        <!--数字只是决定初始化顺序
            默认负数:客户端第一次访问才初始化
            大于零:的数表示服务器启动时,初始化
            数字越小越先初始化
        -->
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>app1</servlet-name>
        <!-- 访问路径前缀 -->
        <url-pattern>/app1/*</url-pattern>
    </servlet-mapping>

</web-app>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

ContextLoaderListener 会创建一个容器 ApplicationContext,解析配置的 xml 文件,走 spring 常规的 Bean 加载流程。 这个 ApplicationContext 会被做为 servlet 的父容器被加载到 servletContext(map)中,当 servlet 被加载的时候会和父容器进行绑定,详见 DispatcherServlet 初始化讲解 3.2

# 代码取代 xml 配置

整体和 xml 是差不多的

public class MyWebApplicationInitializer implements WebApplicationInitializer {
    
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        // Load Spring web application configuration
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(AppConfig.class);

        // Create and register the DispatcherServlet
        DispatcherServlet servlet = new DispatcherServlet(context);
        ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/app/*");
    }

    @ComponentScan("com.fengqianrun.mvc")
    public class AppConfig{

    }
    
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

MyWebApplicationInitializer 被加载是通过 SpringServletContainerInitializer 实现了 ServletContainerInitializer 的规范,在 SpringServletContainerInitializer 中有一个 @HandlesTypes,里面就定义了 WebApplicationInitializer,会把该类加载传入 SpringServletContainerInitializer 的 onStartup 方法,该方法里面继续调用实现了 WebApplicationInitializer 的 onStartup 方法

# 请求实现

我们都知道早出写一个 HttpServlet 并实现 service () 转发具体 doGet doPost 并实现业务逻辑。DispatcherServlet 一样实现了 HttpServlet 并重写了 service、doGet、doPost 等方法,实现具体流程是:

1. 由DispatcherServlet的父类FrameworkServlet具体实现了service、doGet、doPost方法
2. FrameworkServlet会先被调用service()方法,解析方法的请求方式是 get 还是 post,后调用HttpServlet的service()方法进一步判断是调用 doGet 还是 doPost方法
3. HttpServlet在调用FrameworkServlet具体实现了doGet或doPost方法
    3.1 
4. doPost实现会调用FrameworkServlet的子类DispatcherServlet的doService方法
    4.1 doService 首先会打印请求的信息
    4.2 把context添加到 request 的请求中
    4.3 flashMapManager
    4.4 调用 doDispatch 方法
5. doDispatch
    5.1 得到默认的三个 mapping( BeanNameUrlHandlerMapping,RequestMappingHandlerMapping,RouterFunctionMapping,详细在DispatcherServlet 初始化讲解6.2中)
    5.2 便利每个 mapping,并得到请求的路径,根据路径去找 handler(这个handler就是DispatcherServlet 初始化讲解6.2.2的方法),如果是BeanNameUrlHandlerMapping找到的是Bean,RequestMappingHandlerMapping找到的是 HanlerMethod 统一为 handler
    5.3 找到handler后会封装为一个 handler执行链,这个执行链包含了拦截器,所以称为链
    5.4 由于获取的 handler 是一个object,无法确定是BeanNameUrlHandlerMapping的Bean还是RequestMappingHandlerMapping的HandlerMethod,所以执行 getHandlerAdapter 进行适配
        5.4.1 把已经加载的 handlerAdapters 进行便利(DispatcherServlet 初始化讲解 7.5)
        5.4.2 每个 handlerAdapter 实现方式不一样,会有个统一的 supports 方法来判断是否实现了不同 mapping 的要求,并找到合适的 adapter
            5.4.2.1 如 RequestMappingHandlerMapping 对应的是 RequestMappingHandlerAdapter,adapter 的 supports会判断是否是 HandlerMethod 的实例,是则得到这个 adapter
        5.4.3 handler执行链开始执行拦截器,会遍历所有的拦截器执行执行前置方法,如果拦截器前置方法返回false则后面不在执行
        5.4.4 拦截器执行完毕后就可以执行得到的 HandlerAdapter 的 handler 方法,去真正执行 controller 的方法,返回值会封装成 ModelAndView
            5.4.4.1 检查是否限制了请求方式,比如只支持 POST 请求
            5.4.4.2 判断是否开启session锁,用于对持有相同session的请求进行并发限制
            5.4.4.3 执行invokeHandlerMethod方法
                5.4.4.3.1 找出@InitBinder创建一个 binderFactory 工厂,该工厂是对Method请求参数做类型转换,找的是当前Method或全局的@InitBinder的转换器
                5.4.4.3.2 生成ModelFactory,把@ModelAttribute的key和value,以及@SessionAttributes的key和value添加到 Model中,可以保障在controller的Model中得到数据
                5.4.4.3.3 创建一个处理方法的对象,设置方法解析器(解析@RequestParam等注解或对象),返回值解析器(比如加了@ResponseBody要解析成JSON),设置binderFactory
                5.4.4.3.4 创建 ModelAndViewContainer,给ModelAndViewContainer 添加解析的内容(初始化),然后具体去执行 invokAndHandler(req,ModelAndViewContainer),得到更多的信息给 ModelAndViewContainer,比如返回结果
                5.4.4.3.5 最后对 ModelAndViewContainer 进行处理,判断当前请求是否进行了重定向
        5.4.5 通过返回的 ModelAndView 查找并设置视图
        5.4.6 执行拦截器后置方法
        5.4.7 视图渲染
        5.4.8 再调用拦截的执行完成方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
上次更新: 6/11/2025, 4:10:30 PM
注解替代Spring生命周期实现类
SpringBoot 之 Filter、Interceptor、Aspect

← 注解替代Spring生命周期实现类 SpringBoot 之 Filter、Interceptor、Aspect→

Theme by Vdoing | Copyright © 2023-2025
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式