跨境互联网 跨境互联网
首页
  • AI 工具

    • 绘图提示词工具 (opens new window)
    • ChatGPT 指令 (opens new window)
  • ChatGPT

    • ChatGP T介绍
    • ChatGPT API 中文开发手册
    • ChatGPT 中文调教指南
    • ChatGPT 开源项目
  • Midjourney

    • Midjourney 文档
  • Stable Diffusion

    • Stable Diffusion 文档
  • 其他

    • AIGC 热门文章
    • 账号合租 (opens new window)
    • 有趣的网站
  • Vue

    • Vue3前置
  • JAVA基础

    • Stream
    • Git
    • Maven
    • 常用第三方类库
    • 性能调优工具
    • UML系统建模
    • 领域驱动设计
    • 敏捷开发
    • Java 测试
    • 代码规范及工具
    • Groovy 编程
  • 并发编程&多线程

    • 并发编程
    • 高性能队列 Disruptor
    • 多线程并发在电商系统下的应用
  • 其他

    • 面试题
  • 消息中间中间件

    • Kafka
    • RabbitMQ
    • RocketMQ
  • 任务调度

    • Quartz
    • XXL-Job
    • Elastic-Job
  • 源码解析

    • Mybatis 高级使用
    • Mybatis 源码剖析
    • Mybatis-Plus
    • Spring Data JPA
    • Spring 高级使用
    • Spring 源码剖析
    • SpringBoot 高级使用
    • SpringBoot 源码剖析
    • Jdk 解析
    • Tomcat 架构设计&源码剖析
    • Tomcat Web应用服务器
    • Zookeeper 高级
    • Netty
  • 微服务框架

    • 分布式原理
    • 分布式集群架构场景化解决方案
    • Dubbo 高级使用
    • Dubbo 核心源码剖析
    • Spring Cloud Gateway
    • Nacos 实战应用
    • Sentinel 实战应用
    • Seata 分布式事务
  • 数据结构和算法的深入应用
  • 存储

    • 图和Neo4j
    • MongoDB
    • TiDB
    • MySQL 优化
    • MySQL 平滑扩容实战
    • MySQL 海量数据存储与优化
    • Elasticsearch
  • 缓存

    • Redis
    • Aerospike
    • Guava Cache
    • Tair
  • 文件存储

    • 阿里云 OSS 云存储
    • FastDF 文件存储
  • 基础

    • Linux 使用
    • Nginx 使用与配置
    • OpenResty 使用
    • LVS+Keepalived 高可用部署
    • Jekins
  • 容器技术

    • Docker
    • K8S
    • K8S
  • 01.全链路(APM)
  • 02.电商终极搜索解决方案
  • 03.电商亿级数据库设计
  • 04.大屏实时计算
  • 05.分库分表的深入实战
  • 06.多维系统下单点登录
  • 07.多服务之间分布式事务
  • 08.业务幂等性技术架构体系
  • 09.高并发下的12306优化
  • 10.每秒100W请求的秒杀架构体系
  • 11.集中化日志管理平台的应用
  • 12.数据中台配置中心
  • 13.每天千万级订单的生成背后痛点及技术突破
  • 14.红包雨的架构设计及源码实现
  • 人工智能

    • Python 笔记
    • Python 工具库
    • 人工智能(AI) 笔记
    • 人工智能(AI) 项目笔记
  • 大数据

    • Flink流处理框架
  • 加密区

    • 机器学习(ML) (opens new window)
    • 深度学习(DL) (opens new window)
    • 自然语言处理(NLP) (opens new window)
AI 导航 (opens new window)

Revin

首页
  • AI 工具

    • 绘图提示词工具 (opens new window)
    • ChatGPT 指令 (opens new window)
  • ChatGPT

    • ChatGP T介绍
    • ChatGPT API 中文开发手册
    • ChatGPT 中文调教指南
    • ChatGPT 开源项目
  • Midjourney

    • Midjourney 文档
  • Stable Diffusion

    • Stable Diffusion 文档
  • 其他

    • AIGC 热门文章
    • 账号合租 (opens new window)
    • 有趣的网站
  • Vue

    • Vue3前置
  • JAVA基础

    • Stream
    • Git
    • Maven
    • 常用第三方类库
    • 性能调优工具
    • UML系统建模
    • 领域驱动设计
    • 敏捷开发
    • Java 测试
    • 代码规范及工具
    • Groovy 编程
  • 并发编程&多线程

    • 并发编程
    • 高性能队列 Disruptor
    • 多线程并发在电商系统下的应用
  • 其他

    • 面试题
  • 消息中间中间件

    • Kafka
    • RabbitMQ
    • RocketMQ
  • 任务调度

    • Quartz
    • XXL-Job
    • Elastic-Job
  • 源码解析

    • Mybatis 高级使用
    • Mybatis 源码剖析
    • Mybatis-Plus
    • Spring Data JPA
    • Spring 高级使用
    • Spring 源码剖析
    • SpringBoot 高级使用
    • SpringBoot 源码剖析
    • Jdk 解析
    • Tomcat 架构设计&源码剖析
    • Tomcat Web应用服务器
    • Zookeeper 高级
    • Netty
  • 微服务框架

    • 分布式原理
    • 分布式集群架构场景化解决方案
    • Dubbo 高级使用
    • Dubbo 核心源码剖析
    • Spring Cloud Gateway
    • Nacos 实战应用
    • Sentinel 实战应用
    • Seata 分布式事务
  • 数据结构和算法的深入应用
  • 存储

    • 图和Neo4j
    • MongoDB
    • TiDB
    • MySQL 优化
    • MySQL 平滑扩容实战
    • MySQL 海量数据存储与优化
    • Elasticsearch
  • 缓存

    • Redis
    • Aerospike
    • Guava Cache
    • Tair
  • 文件存储

    • 阿里云 OSS 云存储
    • FastDF 文件存储
  • 基础

    • Linux 使用
    • Nginx 使用与配置
    • OpenResty 使用
    • LVS+Keepalived 高可用部署
    • Jekins
  • 容器技术

    • Docker
    • K8S
    • K8S
  • 01.全链路(APM)
  • 02.电商终极搜索解决方案
  • 03.电商亿级数据库设计
  • 04.大屏实时计算
  • 05.分库分表的深入实战
  • 06.多维系统下单点登录
  • 07.多服务之间分布式事务
  • 08.业务幂等性技术架构体系
  • 09.高并发下的12306优化
  • 10.每秒100W请求的秒杀架构体系
  • 11.集中化日志管理平台的应用
  • 12.数据中台配置中心
  • 13.每天千万级订单的生成背后痛点及技术突破
  • 14.红包雨的架构设计及源码实现
  • 人工智能

    • Python 笔记
    • Python 工具库
    • 人工智能(AI) 笔记
    • 人工智能(AI) 项目笔记
  • 大数据

    • Flink流处理框架
  • 加密区

    • 机器学习(ML) (opens new window)
    • 深度学习(DL) (opens new window)
    • 自然语言处理(NLP) (opens new window)
AI 导航 (opens new window)
  • Spring Data JPA
  • MyBatis

  • Spring

  • SpringBoot

    • SpringBoot高级使用

    • SpringBoot源码剖析

      • 介绍
      • 源码剖析-依赖管理
      • 源码剖析-自动配置
      • 源码剖析-Run方法执行流程
      • 源码剖析-自定义Starter
      • 源码剖析-内嵌Tomcat
      • 源码剖析-自动配置SpringMVC
      • 资料
    • Jdk

    • Tomcat

    • Netty

    • 若依

    • Traefik

    • Openresty

    • 开源框架
    • SpringBoot
    • SpringBoot源码剖析
    Revin
    2023-06-15
    目录

    源码剖析-自动配置SpringMVC

    SpringBoot又是如何装配的springMVC呢?

    其实仅仅引入starter是不够的,回忆一下,在一个普通的WEB项目中如何去使用SpringMVC,我们首先就是要在web.xml中配置如下配置

    <servlet>
        <description>spring mvc servlet</description>
        <servlet-name>springMvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springMvc</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    但是在SpringBoot中,我们没有了web.xml文件,我们如何去配置一个Dispatcherservlet呢?其实Servlet3.0规范中规定,要添加一个Servlet,除了采用xml配置或注解的方式,还有一种通过代码的方式,伪代码如下

    servletContext.addServlet(name, this.servlet);
    
    1

    那么也就是说,如果我们能动态往web容器中添加一个我们构造好的DispatcherServlet对象,是不是就实现自动装配SpringMVC了

    # 自动配置(一)自动配置DispatcherServlet和DispatcherServletRegistry

    springboot的自动配置基于SPI机制,实现自动配置的核心要点就是添加一个自动配置的类,SpringBoot MVC的自动配置自然也是相同原理。

    所以,先找到springmvc对应的自动配置类。

    org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
    
    1

    # DispatcherServletAutoConfiguration自动配置类

    @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnWebApplication(type = Type.SERVLET)
    @ConditionalOnClass(DispatcherServlet.class)
    @AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
    public class DispatcherServletAutoConfiguration {
        //...
    }
    
    1
    2
    3
    4
    5
    6
    7
    8

    1、首先注意到,@Configuration表名这是一个配置类,将会被spring给解析。

    2、@ConditionalOnWebApplication意味着当时一个web项目,且是Servlet项目的时候才会被解析。

    3、@ConditionalOnClass指明DispatcherServlet这个核心类必须存在才解析该类。

    4、@AutoConfigureAfter指明在ServletWebServerFactoryAutoConfiguration这个类之后再解析,设定了一个顺序。

    总的来说,这些注解表明了该自动配置类的会解析的前置条件需要满足。

    其次,DispatcherServletAutoConfiguration类主要包含了两个内部类,分别是

    1、DispatcherServletConfiguration

    2、DispatcherServletRegistrationConfiguration

    顾名思义,前者是配置DispatcherServlet,后者是配置DispatcherServlet的注册类。什么是注册类?我们知道Servlet实例是要被添加(注册)到如tomcat这样的ServletContext里的,这样才能够提供请求服务。所以,DispatcherServletRegistrationConfiguration将生成一个Bean,负责将DispatcherServlet给注册到ServletContext中。

    # 配置DispatcherServletConfiguration

    我们先看看DispatcherServletConfiguration这个配置类

    @Configuration(proxyBeanMethods = false)
    @Conditional(DefaultDispatcherServletCondition.class)
    @ConditionalOnClass(ServletRegistration.class)
    @EnableConfigurationProperties({ HttpProperties.class, WebMvcProperties.class })
    protected static class DispatcherServletConfiguration {
    
    
        //...
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    @Conditional指明了一个前置条件判断,由DefaultDispatcherServletCondition实现。主要是判断了是否已经存在DispatcherServlet,如果没有才会触发解析。

    @ConditionalOnClass指明了当ServletRegistration这个类存在的时候才会触发解析,生成的DispatcherServlet才能注册到ServletContext中。

    最后,@EnableConfigrationProperties将会从application.properties这样的配置文件中读取spring.http和spring.mvc前缀的属性生成配置对象HttpProperties和WebMvcProperties。

    再看DispatcherServletConfiguration这个内部类的内部代码

    @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public DispatcherServlet dispatcherServlet(HttpProperties httpProperties, WebMvcProperties webMvcProperties) {
        DispatcherServlet dispatcherServlet = new DispatcherServlet();
        dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
        dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
        dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
        dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
        dispatcherServlet.setEnableLoggingRequestDetails(httpProperties.isLogRequestDetails());
        return dispatcherServlet;
    }
    
    
    @Bean
    @ConditionalOnBean(MultipartResolver.class)
    @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
    public MultipartResolver multipartResolver(MultipartResolver resolver) {
        // Detect if the user has created a MultipartResolver but named it incorrectly
        return resolver;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

    这个两个方法我们比较熟悉了,就是生成了Bean。

    dispatcherServlet方法将生成一个DispatcherServlet的Bean对象。比较简单,就是获取一个实例,然后添加一些属性设置。

    multipartResolver方法主要是把你配置的MultipartResolver的Bean给重命名一下,防止你不是用multipartResolver这个名字作为Bean的名字。

    # 配置DispatcherServletRegistrationConfiguration

    再看注册类的Bean配置

    @Configuration(proxyBeanMethods = false)
    @Conditional(DispatcherServletRegistrationCondition.class)
    @ConditionalOnClass(ServletRegistration.class)
    @EnableConfigurationProperties(WebMvcProperties.class)
    @Import(DispatcherServletConfiguration.class)
    protected static class DispatcherServletRegistrationConfiguration {
        //...
    }
    
    1
    2
    3
    4
    5
    6
    7
    8

    同样的,@Conditional有一个前置判断,DispatcherServletRegistrationCondition主要判断了该注册类的Bean是否存在。

    @ConditionOnClass也判断了ServletRegistration是否存在

    @EnableConfigurationProperties生成了WebMvcProperties的属性对象

    @Import导入了DispatcherServletConfiguration,也就是我们上面的配置对象。

    再看DispatcherServletRegistrationConfiguration的内部实现

    @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
    @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
            WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
        DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
                webMvcProperties.getServlet().getPath());
        registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
        registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
        multipartConfig.ifAvailable(registration::setMultipartConfig);
        return registration;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    内部只有一个方法,生成了DispatcherServletRegistrationBean。核心逻辑就是实例化了一个Bean,设置了一些参数,如dispatcherServlet、loadOnStartup等

    # 总结

    springboot mvc的自动配置类是DispatcherServletAutoConfigration,主要做了两件事:

    1)配置DispatcherServlet

    2)配置DispatcherServlet的注册Bean(DispatcherServletRegistrationBean)

    # 自动配置(二)注册DispatcherServlet到ServletContext

    在上一小节的源码翻阅中,我们看到了DispatcherServlet和DispatcherServletRegistrationBean这两个Bean的自动配置。DispatcherServlet我们很熟悉,DispatcherServletRegistrationBean负责将DispatcherServlet注册到ServletContext当中

    # DispatcherServletRegistrationBean的类图

    既然该类的职责是负责注册DispatcherServlet,那么我们得知道什么时候触发注册操作。为此,我们先看看DispatcherServletRegistrationBean这个类的类图

    image-20201123192032882

    # 注册DispatcherServlet流程

    # ServletContextInitializer

    我们看到,最上面是一个ServletContextInitializer接口。我们可以知道,实现该接口意味着是用来初始化ServletContext的。我们看看该接口

    public interface ServletContextInitializer {
        void onStartup(ServletContext servletContext) throws ServletException;
    }
    
    1
    2
    3

    # RegistrationBean

    看看RegistrationBean是怎么实现onStartup方法的

    @Override
    public final void onStartup(ServletContext servletContext) throws ServletException {
        String description = getDescription();
        if (!isEnabled()) {
            logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
            return;
        }
        
        register(description, servletContext);
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    调用了内部register方法,跟进它

    protected abstract void register(String description, ServletContext servletContext);
    
    1

    这是一个抽象方法

    # DynamicRegistrationBean

    再看DynamicRegistrationBean是怎么实现register方法的

    @Override
    protected final void register(String description, ServletContext servletContext) {
        D registration = addRegistration(description, servletContext);
        if (registration == null) {
            logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)");
            return;
        }
        configure(registration);
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    跟进addRegistration方法

    protected abstract D addRegistration(String description, ServletContext servletContext);
    
    1

    一样是一个抽象方法

    # ServletRegistrationBean

    再看ServletRegistrationBean是怎么实现addRegistration方法的

    @Override
    protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
        String name = getServletName();
        return servletContext.addServlet(name, this.servlet);
    }
    
    1
    2
    3
    4
    5

    我们看到,这里直接将DispatcherServlet给add到了servletContext当中。

    # SpringBoot启动流程中具体体现

    getSelfInitializer().onStartup(servletContext);
    
    1

    这段代码其实就是去加载SpringMVC,那么他是如何做到的呢?getSelfInitializer()最终会去调用到ServletWebServerApplicationContext的selfInitialize方法,该方法代码如下

    image-20201109144627132

    private void selfInitialize(ServletContext servletContext) throws ServletException {
        prepareWebApplicationContext(servletContext);
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes(
                beanFactory);
        WebApplicationContextUtils.registerWebApplicationScopes(beanFactory,
                getServletContext());
        existingScopes.restore();
        WebApplicationContextUtils.registerEnvironmentBeans(beanFactory,
                getServletContext());
        for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
            beans.onStartup(servletContext);
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    我们通过调试,知道getServletContextInitializerBeans()返回的是一个ServletContextInitializer集合,集合中有以下几个对象

    image-20201109145059052

    然后依次去调用对象的onStartup方法,那么对于上图标红的对象来说,就是会调用到DispatcherServletRegistrationBean的onStartup方法,这个类并没有这个方法,所以最终会调用到父类RegistrationBean的onStartup方法,该方法代码如下

    public final void onStartup(ServletContext servletContext) throws ServletException {
        //获取当前环境到底是一个filter 还是一个servlet 还是一个listener
        String description = getDescription();
        if (!isEnabled()) {
            logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
            return;
        }
        register(description, servletContext);
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    这边register(description, servletContext);会调用到DynamicRegistrationBean的register方法,代码如下

    protected final void register(String description, ServletContext servletContext) {
        D registration = addRegistration(description, servletContext);
        if (registration == null) {
            logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)");
            return;
        }
        configure(registration);
    }
    
    1
    2
    3
    4
    5
    6
    7
    8

    addRegistration(description, servletContext)又会调用到ServletRegistrationBean中的addRegistration方法,代码如下

    protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
      String name = getServletName();
      return servletContext.addServlet(name, this.servlet);
    }
    
    1
    2
    3
    4

    看到了关键的servletContext.addServlet代码了,我们通过调试,即可知到this.servlet就是dispatcherServlet

    image-20201109145451122

    总结

    SpringBoot自动装配SpringMvc其实就是往ServletContext中加入了一个Dispatcherservlet。 Servlet3.0规范中有这个说明,除了可以动态加Servlet,还可以动态加Listener,Filter

    • addServlet
    • addListener
    • addFilter
    上次更新: 2025/04/03, 11:07:08
    源码剖析-内嵌Tomcat
    资料

    ← 源码剖析-内嵌Tomcat 资料→

    最近更新
    01
    tailwindcss
    03-26
    02
    PaddleSpeech
    02-18
    03
    whisper
    02-18
    更多文章>
    Theme by Vdoing | Copyright © 2019-2025 跨境互联网 | 豫ICP备14016603号-5 | 豫公网安备41090002410995号
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式