Spring mvc原理之注册DispatcherServlet

2024-01-25 04:12

本文主要是介绍Spring mvc原理之注册DispatcherServlet,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

背景

spring mvc作为优秀的web框架,从2003年问世(根据changelog)到现在已经经历了21年。springframework框架里,web相关的类从1.0版本的25个,发展到现在6.1版本,已经有103个。还不包括spring-boot里web相关的代码。初学者使用spring-boot-starter-web 能很快启动一个web服务,但是要理清内部的运行逻辑和理解作者的设计思路,就要花费很大力气。

下面我尝试模仿spring mvc,从0开始搭建web服务,剖析作者的设计意图。

web服务的基础-Tomcat

tomcat作为开源轻量级web服务器,支持java servlet,是spring boot默认的web服务器。通过内嵌的tomcat,我们可以很快速的开发web应用。我们通过一个demo,看一下开发web应用需要的最小配置。

一个小Demo

引入内嵌tomcat的pom文件:

<dependency><groupId>jakarta.annotation</groupId><artifactId>jakarta.annotation-api</artifactId><version>2.1.1</version><scope>compile</scope>
</dependency>
<dependency><groupId>org.apache.tomcat.embed</groupId><artifactId>tomcat-embed-core</artifactId><version>10.1.18</version><scope>compile</scope>
</dependency>

然后注册一个servlet就可以对外提供服务。

public static void main(String[] args) throws LifecycleException {
Tomcat tomcat = new Tomcat();String path = "C:\\\\Users\\\\admin\\\\AppData\\\\Local\\\\Temp\\\\tomcat.default.9999";tomcat.setBaseDir(path);Context context = tomcat.addContext("", path);tomcat.addServlet(context.getPath(), "defaultServlet", new HttpServlet() {@Overridepublic void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("get request " + req.getRequestURL());resp.setStatus(200);PrintWriter writer = resp.getWriter();writer.println(System.currentTimeMillis());}});context.addServletMappingDecoded("/*", "defaultServlet");Connector connector = new Connector("HTTP/1.1");connector.setPort(9999);tomcat.setConnector(connector);tomcat.start();tomcat.getServer().await();
}

这里做了几件事情:

  1. 初始化Tomcat实例,并设置根目录
  2. 创建一个Context
  3. 创建一个Servlet,并添加url路径到Servlet的映射关系
  4. 添加Connector,监听9999端口,这里支持HTTP/1.1协议
  5. 启动tomcat服务

访问http://localhost:9999就能看到正常返回了结果:

> curl -i <http://localhost:9999/abc/dd?xx=1>
HTTP/1.1 200
Content-Length: 15
Date: Wed, 24 Jan 2024 04:05:39 GMT1706069139905

Spring boot里的tomcat配置

了解完tomcat的基本使用方式,再对比spring boot里tomcat的用法。下面是spring boot初始化tomcat的逻辑:

// org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {if (this.disableMBeanRegistry) {Registry.disableRegistry();}Tomcat tomcat = new Tomcat();File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");tomcat.setBaseDir(baseDir.getAbsolutePath());for (LifecycleListener listener : this.serverLifecycleListeners) {tomcat.getServer().addLifecycleListener(listener);}Connector connector = new Connector(this.protocol);connector.setThrowOnFailure(true);tomcat.getService().addConnector(connector);customizeConnector(connector);tomcat.setConnector(connector);tomcat.getHost().setAutoDeploy(false);configureEngine(tomcat.getEngine());for (Connector additionalConnector : this.additionalTomcatConnectors) {tomcat.getService().addConnector(additionalConnector);}prepareContext(tomcat.getHost(), initializers);return getTomcatWebServer(tomcat);
}

它这里干了几个事:

  1. 初始化Tomcat实例,并设置根目录
  2. 添加Server容器的LifecycleListener,作为一个扩展点开放出来
  3. 添加默认Connector,这里支持自定义Connector的属性,包括设置端口、协议等,又是一个扩展点
  4. 配置tomcat的Engine的backgroundProcessorDelay 属性和添加自定义engineValves
  5. 添加用户定义的Connector
  6. 创建一个Context,类型是spring自己定义的,继承了tomcat的Context
  7. 将Tomcat对象包装成spring的TomcatWebServer 对象

在不考虑内部细节的情况下,spring boot初始化tomcat的步骤基本和demo里的步骤是一样的。主要增加了很多扩展点,可以添加Server容器、自定义Connector、添加Engine的Valve(管道处理类)。还封装了tomcat的Context,在自己的Context里也加了很多扩展点。

另外,有一点很大的不同,就是spring boot的初始流程里没用看到注册Servlet的地方。我们都知道spring mvc核心的Servlet是DispatcherServlet,它会代理所有请求。下面分析DispatcherServlet是怎么注册到tomcat的Context。

DispatcherServlet注册到tomcat容器

spring boot是通过ServletContextInitializer来注册tomcat的Servlet、Filter、Listener等对象到ServletContext里。所以第一步要先将DispatcherServlet转成ServletContextInitializer对象。

DispatcherServlet怎么转成ServletContextInitializer对象

DispatcherServlet是一个Servlet类型,要转成ServletContextInitializer,需要一个包装类,DispatcherServletRegistrationBean就是这个包装类。 DispatcherServletRegistrationBean是ServletContextInitializer的子类,在spring boot启动时,通过自动装配机制,注册了DispatcherServletRegistrationBean。并且将DispatcherServlet对象放到了DispatcherServletRegistrationBean对象里。 DispatcherServletRegistrationBean的注册流程:

  1. DispatcherServletAutoConfiguration配置类先初始化了DispatcherServlet、DispatcherServletRegistrationBean对象
  2. 在DispatcherServletRegistrationBean对象里放入注册Servlet需要的信息
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;
}

在得到ServletContextInitializer后就要考虑什么时候去执行它,并将DispatcherServlet注册到tomcat容器。这时先看一下tomcat的初始化过程。

tomcat创建时机

SpringApplication.run()方法在执行refreshContext(context)时,会调用AnnotationConfigServletWebServerApplicationContext.refresh()方法,一直会调用到web容器的父类ServletWebServerApplicationContext的createWebServer()。

生成tomcat对象

createWebServer()会调用TomcatServletWebServerFactory.getWebServer()来初始化tomcat对象。

初始化tomcat对象需要设置一个tomcat上下文,对应类型是StandardContext。这里spring自定义了StandardContext的子类TomcatEmbeddedContext作为tomcat上下文。

这时spring会预定义3个ServletContextInitializer,并封装到TomcatStarter里。TomcatStarter是ServletContainerInitializer的子类,ServletContainerInitializer是tomcat的对象和ServletContextInitializer不一样,后者是spring的对象。 然后调用TomcatEmbeddedContext.addServletContainerInitializer(TomcatStarter),把TomcatStarter添加到tomcat上下文的initializers属性里。initializers属性在启动tomcat时会用到。

tomcat初始化后,会被包装成TomcatWebServer对象,然后在构造函数里启动tomcat。之后tomcat就会从上下文对象里拿到ServletContextInitializer进行初始化。

tomcat初始化流程

  1. tomcat.start()调用server.start()
  2. server又调用service.start()
  3. service又调用engine.start()
  4. engine通过线程池调用host.start()
  5. host通过线程池调用TomcatEmbeddedContext.start(),这会执行父类方法StandardContext.startInternal(),方法里会把上面initializers里的ServletContainerInitializer对象拿出来,也就是上面的TomcatStarter对象,调用它的onStartup()方法
  6. TomcatStarter又会调用它里面的几个ServletContextInitializer.onstartup(servletContext) TomcatStarter默认包含3个ServletContextInitializer,我们关注的是ServletWebServerApplicationContext.selfInitialize()方法对应的匿名ServletContextInitializer,代码如下:
private void selfInitialize(ServletContext servletContext) throws ServletException {prepareWebApplicationContext(servletContext);registerApplicationScope(servletContext);WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);for (ServletContextInitializer beans : getServletContextInitializerBeans()) {beans.onStartup(servletContext);}
}

核心逻辑在getServletContextInitializerBeans()里,方法返回ServletContextInitializerBeans对象。 ServletContextInitializerBeans是ServletContextInitializer的集合,它会把beanFactory里的ServletContextInitializer对象加进来,并且还把Servlet、Fileter、Listerner等spring bean包装成RegistrationBean(RegistrationBean是ServletContextInitializer的子类)也加进来。这样就得到一个ServletContextInitializer列表,默认会加载的对象有:

  1. DispatcherServletRegistrationBean
  2. CharacterEncodingFilter
  3. OrderedFormContentFilter
  4. OrderedRequestContextFilter

然后调用每个ServletContextInitializer的onStartup(servletContext)方法。

DispatcherServletRegistrationBean注册DispatcherServlet

注册DispatcherServlet要执行这两行代码:

// 添加servlet
servletContext.addServlet(servletName, servlet);// 添加映射关系
context.addServletMappingDecoded(urlPattern, servletName);

DispatcherServletRegistrationBean里onStratup方法会调用register()方法。register()方法会做两个事情:

  1. 执行addRegistration()方法,会把里面的DispatcherServlet对象注册到ServletContext,并返回ServletRegistration对象。

    protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {String name = getServletName();return servletContext.addServlet(name, this.servlet);
    }
    
  2. 执行configure()方法,注册url到DispatcherServlet对象的映射,逻辑在registration.addMapping(urlMapping)里

    // org.springframework.boot.web.servlet.ServletRegistrationBean#configure
    protected void configure(ServletRegistration.Dynamic registration) {// ...if (!ObjectUtils.isEmpty(urlMapping)) {registration.addMapping(urlMapping);}// ...
    }// org.apache.catalina.core.ApplicationServletRegistration#addMapping
    public Set<String> addMapping(String... urlPatterns) {// ...for (String urlPattern : urlPatterns) {context.addServletMappingDecoded(UDecoder.URLDecode(urlPattern, StandardCharsets.UTF_8), wrapper.getName());}return Collections.emptySet();
    }
    

到这里,DispatcherServlet就成功注册到了toncat的上下文上,并且和url建立了映射关系,默认url是"/"。

总结

spring boot的基础是tomcat,就要遵循tomcat的servlet规范。它通过ServletContextInitializer实现了Servlet的自动注册机制;用DispatcherServlet代理所有请求,内部实现了请求的路由、类型转换等。将开发者和tomcat解耦,也方便框架去替换不同的web容器。

这篇关于Spring mvc原理之注册DispatcherServlet的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


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

相关文章

SpringCloud中的@FeignClient注解使用详解

《SpringCloud中的@FeignClient注解使用详解》在SpringCloud中使用Feign进行服务间的调用时,通常会使用@FeignClient注解来标记Feign客户端接口,这篇文章... 在Spring Cloud中使用Feign进行服务间的调用时,通常会使用@FeignClient注解

Java Spring 中的监听器Listener详解与实战教程

《JavaSpring中的监听器Listener详解与实战教程》Spring提供了多种监听器机制,可以用于监听应用生命周期、会话生命周期和请求处理过程中的事件,:本文主要介绍JavaSprin... 目录一、监听器的作用1.1 应用生命周期管理1.2 会话管理1.3 请求处理监控二、创建监听器2.1 Ser

JVisualVM之Java性能监控与调优利器详解

《JVisualVM之Java性能监控与调优利器详解》本文将详细介绍JVisualVM的使用方法,并结合实际案例展示如何利用它进行性能调优,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全... 目录1. JVisualVM简介2. JVisualVM的安装与启动2.1 启动JVisualVM2

Java如何从Redis中批量读取数据

《Java如何从Redis中批量读取数据》:本文主要介绍Java如何从Redis中批量读取数据的情况,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一.背景概述二.分析与实现三.发现问题与屡次改进3.1.QPS过高而且波动很大3.2.程序中断,抛异常3.3.内存消

SpringBoot使用ffmpeg实现视频压缩

《SpringBoot使用ffmpeg实现视频压缩》FFmpeg是一个开源的跨平台多媒体处理工具集,用于录制,转换,编辑和流式传输音频和视频,本文将使用ffmpeg实现视频压缩功能,有需要的可以参考... 目录核心功能1.格式转换2.编解码3.音视频处理4.流媒体支持5.滤镜(Filter)安装配置linu

apache的commons-pool2原理与使用实践记录

《apache的commons-pool2原理与使用实践记录》ApacheCommonsPool2是一个高效的对象池化框架,通过复用昂贵资源(如数据库连接、线程、网络连接)优化系统性能,这篇文章主... 目录一、核心原理与组件二、使用步骤详解(以数据库连接池为例)三、高级配置与优化四、典型应用场景五、注意事

在Spring Boot中实现HTTPS加密通信及常见问题排查

《在SpringBoot中实现HTTPS加密通信及常见问题排查》HTTPS是HTTP的安全版本,通过SSL/TLS协议为通讯提供加密、身份验证和数据完整性保护,下面通过本文给大家介绍在SpringB... 目录一、HTTPS核心原理1.加密流程概述2.加密技术组合二、证书体系详解1、证书类型对比2. 证书获

Java使用MethodHandle来替代反射,提高性能问题

《Java使用MethodHandle来替代反射,提高性能问题》:本文主要介绍Java使用MethodHandle来替代反射,提高性能问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑... 目录一、认识MethodHandle1、简介2、使用方式3、与反射的区别二、示例1、基本使用2、(重要)

Java实现本地缓存的常用方案介绍

《Java实现本地缓存的常用方案介绍》本地缓存的代表技术主要有HashMap,GuavaCache,Caffeine和Encahche,这篇文章主要来和大家聊聊java利用这些技术分别实现本地缓存的方... 目录本地缓存实现方式HashMapConcurrentHashMapGuava CacheCaffe

SpringBoot整合Sa-Token实现RBAC权限模型的过程解析

《SpringBoot整合Sa-Token实现RBAC权限模型的过程解析》:本文主要介绍SpringBoot整合Sa-Token实现RBAC权限模型的过程解析,本文给大家介绍的非常详细,对大家的学... 目录前言一、基础概念1.1 RBAC模型核心概念1.2 Sa-Token核心功能1.3 环境准备二、表结