SpringMVC源码深度解析(上)

        今天,聊聊SpringMVC框架的原理。SpringMVC属于Web框架,它不能单独存在,需要依赖Servlet容器,常用的Servlet容器有Tomcat、Jetty等,这里以Tomcat为例进行讲解。老规矩,先看看本项目的层级结构:

        需要的依赖为:

plugins {id 'java'id 'war'
}group 'org.springframework'
version '5.3.10-SNAPSHOT'repositories {mavenCentral()
}dependencies {compile(project(":spring-web"))compile(project(":spring-webmvc"))testImplementation 'junit:junit:4.11'testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'compile group: 'javax.servlet', name: 'javax.servlet-api', version: '3.1.0'compileOnly group: 'javax.servlet', name: 'javax.servlet-api', version: '4.0.0'compile group: 'javax.servlet.jsp', name: 'javax.servlet.jsp-api' ,version: '2.3.1'compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.9.4'compile group: 'commons-fileupload', name: 'commons-fileupload', version: '1.3.1'compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.4'compile group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.9.4'implementation group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '9.0.33'implementation group: 'org.apache.tomcat.embed', name: 'tomcat-embed-jasper', version: '9.0.33'
}test {useJUnitPlatform()
}

        启动类为Starter,代码如下:        

package com.szl;import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.startup.Tomcat;public class Starter {private static int port = 9000;private static String contextPath = "/";public static void main(String[] args) throws Exception {Tomcat tomcat = new Tomcat();String baseDir = Thread.currentThread().getContextClassLoader().getResource("").getPath();tomcat.setBaseDir(baseDir);tomcat.setPort(port);Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");connector.setPort(port);tomcat.setConnector(connector);tomcat.addWebapp(contextPath, baseDir);tomcat.enableNaming();try {tomcat.start();} catch (LifecycleException e) {System.err.println("tomcat 启动失败...");}tomcat.getServer().await();}
}

        在Starter类中,会启动Tomcat容器,这里面的代码属于固定写法,熟悉Spring Boot源码的朋友肯定知道,在Spring Boot中,启动Tomcat代码也是如此。然后在resources目录下,新建目录:META-INF/services,在该目录下,创建一个文件:javax.servlet.ServletContainerInitializer,这是一个接口的全限定名,里面内容为该接口的实现类的全限定名:

org.springframework.web.SpringServletContainerInitializer

        如果你看过其他的框架源代码,比如Dubbo、Spring Boot等,你就会知道,这属于SPI机制(Service Provider Interface),SpringServletContainerInitializer实现了ServletContainerInitializer接口。这属于J2EE的规范,因此,Servlet容器会实现。最终,SpringServletContainerInitializer会被实例化,并调用SpringServletContainerInitializer#onStartup()方法。这些操作不需要我们来做,是Tomcat在启动的时候帮我们做的,我们要做的就是在onStartup()方法中实现逻辑即可,而SpringServletContainerInitializer很明显是Spring提供的,看看该类的onStartup()方法,代码如下:

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {@Overridepublic void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)throws ServletException {List<WebApplicationInitializer> initializers = Collections.emptyList();if (webAppInitializerClasses != null) {initializers = new ArrayList<>(webAppInitializerClasses.size());for (Class<?> waiClass : webAppInitializerClasses) {// 接口和抽象类servlet容器也会给我们,但是我们不要// 排除接口和容器if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&WebApplicationInitializer.class.isAssignableFrom(waiClass)) {try {// 通过实例化并添加到集合中initializers.add((WebApplicationInitializer)ReflectionUtils.accessibleConstructor(waiClass).newInstance());} catch (Throwable ex) {throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);}}}}if (initializers.isEmpty()) {servletContext.log("No Spring WebApplicationInitializer types detected on classpath");return;}servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");AnnotationAwareOrderComparator.sort(initializers);// 调用initializer.onStartup  进行扩展for (WebApplicationInitializer initializer : initializers) {initializer.onStartup(servletContext);}}}

        其中,@HandlesTypes注解会指定一个Class类型,也就是onStartup()方法的第一个入参类型。我还没研究过Tomcat的源码,我猜应该是Tomcat启动的时候,会从它自己的ClassLoader中获取到所有@HandlesTypes注解指定的Class,在调用onStartup()方法的时候传入。此时传入的就是所有实现了WebApplicationInitializer的类,也包括抽象类、接口等,因此需要过滤。当然也包括我自己写的MyWebApplicationInitializer类,通过反射实例化,放入到一个List中,最后遍历,调用WebApplicationInitializer#onStartup()方法。看看 MyWebApplicationInitializer类:

package com.szl.initialize;import com.szl.config.RootConfig;
import com.szl.config.WebAppConfig;
import com.szl.listener.AppStartedApplicationListener;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;public class MyWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {protected ApplicationContextInitializer<?>[] getServletApplicationContextInitializers() {return new ApplicationContextInitializer[]{(applicationContext) -> {applicationContext.addApplicationListener(new AppStartedApplicationListener());}};}/*** 返回父容器的配置类* @return*/@Overrideprotected Class<?>[] getRootConfigClasses() {return new Class[]{RootConfig.class};}/*** 返回Web容器的配置类* @return*/@Overrideprotected Class<?>[] getServletConfigClasses() {return new Class[]{WebAppConfig.class};}/*** 返回 DispatcherServlet的映射路径* @return*/@Overrideprotected String[] getServletMappings() {return new String[]{"/"};}
}

        最主要是实现 getRootConfigClasses()、getServletConfigClasses()、getServletMappings()方法等,其中前两个方法是实现SpringMVC父子容器的核心,分别返回的是RootConfig.class和WebAppConfig.class看看这两个类:

package com.szl.config;import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;import static org.springframework.context.annotation.FilterType.ASSIGNABLE_TYPE;@Configuration
@ComponentScan(basePackages = "com.szl", excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Controller.class}),@ComponentScan.Filter(type = ASSIGNABLE_TYPE, value = WebAppConfig.class),
})
public class RootConfig {}
package com.szl.config;import com.szl.interceptor.MyInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;@Configuration
@ComponentScan(basePackages = {"com.szl"}, includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = {RestController.class, Controller.class})
}, useDefaultFilters = false)
@EnableWebMvc   // = <mvc:annotation-driven/>
public class WebAppConfig implements WebMvcConfigurer {@Beanpublic MyInterceptor interceptor() {return new MyInterceptor();}@Beanpublic MultipartResolver multipartResolver() {CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();multipartResolver.setDefaultEncoding("UTF-8");multipartResolver.setMaxUploadSize(1024 * 1024 * 10);return multipartResolver;}/*	@Beanpublic AcceptHeaderLocaleResolver localeResolver() {AcceptHeaderLocaleResolver acceptHeaderLocaleResolver = new AcceptHeaderLocaleResolver();return acceptHeaderLocaleResolver;}*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(interceptor()).addPathPatterns("/*");}@Beanpublic InternalResourceViewResolver internalResourceViewResolver() {InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();viewResolver.setSuffix(".jsp");viewResolver.setPrefix("/WEB-INF/jsp/");return viewResolver;}/*@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {converters.add(new MappingJackson2HttpMessageConverter());}*/}

        主要就是这两个类上的@ComponentScan注解,熟悉Springd的朋友应该知道,该注解用于指定要扫描的包路径。RootConfig上的@ComponentScan注解表示扫描的是:com.szl下的所有类,但是排除被@Controller注解修饰的类以及WebAppConfig类

        WebAppConfig的上@ComponentScan注解表示扫描的是:com.szl下的所有类,但是这些类必须是被@Controller或者@RestController注所修饰

        因此可以知道,RootConfig扫描的是非Web相关类,WebAppConfig扫描的是Web相关类。同时,MyWebApplicationInitializer#onStartup()方法,但是该方法是在其父类中实现的,代码如下:

        这里会调用AbstractAnnotationConfigDispatcherServletInitializer#getRootConfigClasses()方法,返回的数组只有RootConfig.class一个元素,创建AnnotationConfigWebApplicationContext对象,调用AnnotationConfigWebApplicationContext#register()方法,传入RootConfig.class,进行注册。创建ContextLoaderListener,调有参构造,传入AnnotationConfigWebApplicationContext对象,代码如下:

        并将ContextLoaderListener对象添加到ServletContext的监听器中,Tomcat启动的是会调用,最终调用到 ContextLoaderListener#contextInitialized()方法,代码如下:

        到这里为止,父容器已经完成了初始化,并且可以通过servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)方法获取到父容器。OK,再回到AbstractDispatcherServletInitializer#onStartup()方法中,代码如下:

        重点看看AbstractDispatcherServletInitializer#createServletApplicationContext()方法,代码如下:

        可知,会创建 AnnotationConfigWebApplicationContext对象,并将WebAppConfig注册到Web容器中,并创建DispatcherServlet,调用其有参构造,传入Web容器。并调用AbstractDispatcherServletInitializer#getServletApplicationContextInitializers()方法(该方法为抽象方法,在子类中实现,我在MyWebApplicationInitializer中实现了),返回的是ApplicationContextInitializer的集合,并将其设置到DispatcherServlet对象中的 contextInitializers属性中,这是SpringMVC的扩展点。调用ServletContext#addServlet(),传入DispatcherServlet对象和servletName,即"dispatcher",调用AbstractDispatcherServletInitializer#registerServletFilter()方法,代码如下:

        顺便看看MyWebApplicationInitializer#getServletApplicationContextInitializers(),代码如下:

        到这里为止,就聊完了MyWebApplicationInitializer是如何将DispatcherServlet对象注册到Web服务(Tomcat)的。而DispatcherServlet是SpringMVC的核心,它是一个Servlet对象。如果有请求到了DispatcherServlet这里,再通过它进行请求的分发,由它决定将具体调用哪个 Controller。

        先看看DispatcherServlet的继承图,如下:

        如果对Servlet熟悉的话,会知道Tomcat会自动调用GenericServlet#init(ServletConfig config)方法,代码如下:

        重点看看HttpServletBean#initServletBean()方法,这里会做初始化处理,以及调用 AbstractApplicationContext#refresh(方法等,代码如下:

        再看看FrameworkServlet#onRefresh()方法,代码如下:

        随便看几个,如下所示:

        剩下的几个组件:包括国际化、主题等等,这些有兴趣自己看看,我就不说了。以上几个组件,后面用到的时候都会讲。到现在为止,SpringMVC与Tomcat的整合以及SpringMVC的初始化讲完了。

        至于SpringMVC的父子容器,我多说两句:我觉得这没什么特殊的,就是创建两个AnnotationConfigWebApplicationContext对象,其中一个存储非Web相关的类(没有被@Controller、@RestController),他是父容器;另一个当然就是存储Web相关的类,它是子容器,并且将父容器赋值给子容器的parent属性。如果要获取某个Bean对象,首先调用子容器的getBean()方法,如果获取不到Bean对象,就调用父容器的getBean()方法获取Bean对象。我觉得不分父子容器,把所有的Bean对象都存储在一个容器中,也是可以的。

        剩下的内容讲DispatcherServler的流程,这将在下一篇博客《SpringMVC源码深度解析(中)》中讲,敬请期待~

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

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

相关文章

【入门基础】java泛型和通配符详解

【入门基础】java泛型和通配符详解 文章目录 前言泛型类泛型方法泛型接口通配符&#xff08;Wildcards&#xff09;使用场景非主流用法 总结 前言 Java泛型&#xff08;Generics&#xff09;是JDK 5中引入的一个新特性&#xff0c;它提供了编译时类型安全检测机制&#xff0c;…

socket 收发TCP/UDP

一、c 个人测试记录&#xff0c;有问题还请指出&#xff0c;谢谢 参考&#xff1a;C开发基础之网络编程WinSock库使用详解TCP/UDP Socket开发_c udp使用什么库-CSDN博客 代码中Logger测试见文章&#xff1a; c中spdlog的使用/python中logger的使用-CSDN博客 1、main.cpp 收…

【体外诊断】ARM/X86+FPGA嵌入式计算机在医疗CT机中的应用

体外诊断 信迈科技提供基于Intel平台、AMD平台、NXP平台的核心板、2.5寸主板、Mini-ITX主板、4寸主板、PICO-ITX主板&#xff0c;以及嵌入式准系统等计算机硬件。产品支持GAHDMI等独立双显&#xff0c;提供丰富串口、USB、GPIO、PCIe扩展接口等I/O接口&#xff0c;扩展性强&…

前端组件化开发:以Vue自定义底部操作栏组件为例

摘要 随着前端技术的不断演进&#xff0c;组件化开发逐渐成为提升前端开发效率和代码可维护性的关键手段。本文将通过介绍一款Vue自定义的底部操作栏组件&#xff0c;探讨前端组件化开发的重要性、实践过程及其带来的优势。 一、引言 随着Web应用的日益复杂&#xff0c;传统的…

通义千问AI模型对接飞书机器人-模型配置(2-1)

一 背景 根据业务或者使用场景搭建自定义的智能ai模型机器人&#xff0c;可以较少我们人工回答的沟通成本&#xff0c;而且可以更加便捷的了解业务需求给出大家设定的业务范围的回答&#xff0c;目前基于阿里云的通义千问模型研究。 二 模型研究 参考阿里云帮助文档&#xf…

CSRF+XSS组合攻击实战

目录 0x01安装靶场 0x02分析功能点的请求接口&#xff0c;构造恶意请求 0x03寻找xss漏洞 0x01安装靶场 下载源码&#xff0c;解压到网站根目录 1.修改数据库配置文件 打开源码&#xff0c;进入到include目录下&#xff0c;打开数据库配置文件database.inc.php 将数据库的…

组内第一次会议

会议内容 1、科研平台使用 增删改查对文件 cp -r /root/mmdetection/dataset/ /root/user/wbzExperiment/mmdetection/ rm -r /root/user/yolov5-master tar -czvf test03.tar.gz test03/ unzip abc.zip 上传文件、解压文件&#xff1a;要在自己的目录中&#xff0c;进入…

Python函数基础:构建代码逻辑的基石(补全篇)

在前面我已经编写过一篇&#xff0c;python函数基础的博文&#xff0c;相信有基础的同学应该看得出来&#xff0c;那一篇的基础内容也是不全的&#xff0c;于是就有了这个补全篇。补全篇&#xff0c;补充了变量的作用与&#xff08;global与nonlocal&#xff09;、递归函数、闭…

acwing796-子矩阵的和-前缀和

s矩阵是全局变量&#xff0c;维度n*m,从1~n和 1~m存储元素【0】【0】~【0】【m】和【0】【0】~【n】【0】分别存储的都是0.s矩阵刚开始是存储输入的元素&#xff0c;后面用于存储前缀和。 s矩阵的意思是s【i】【j】表示从【0】【0】到【i】【j】为对角线的矩阵里面所有元素的和…

多类别支持向量机(Multi-class SVM)

多类别支持向量机&#xff08;Multi-class SVM&#xff09;是一种扩展二分类支持向量机以处理多类别分类问题的方法。常见的方法有“一对一”&#xff08;one-vs-one&#xff09;和“一对多”&#xff08;one-vs-rest&#xff09;。 一、数学模型理论推导 1.1 一对多&#xf…

新的铸造厂通过 PROFIBUS 技术实现完全自动化

钢铁生产商某钢以其在厚钢板类别中极高的产品质量而闻名。其原材料&#xff08;板坯连铸机&#xff09;在钢铁厂本地生产&#xff0c;该厂最近新建了一座垂直连铸厂。该项目的一个主要目标是从一开始就完全自动化这座新工厂和整个铸造过程&#xff0c;以高成本效率实现最佳产品…

用AI对抗AI:Fortinet解锁家电制造网络安全新密码

Fortinet盛大启幕《构筑垂直行业 网络安全防线》系列研讨会。首场研讨会聚焦于家电制造领域&#xff0c;以《利用AI打造家电制造网络安全的新质力》为主题。 Fortinet中国南区资深安全顾问黄志攀深入洞察家电制造行业的网络安全挑战&#xff0c;全面解析了Fortinet如何通过全栈…

数据库系统概论:数据库系统的锁机制

引言 锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中&#xff0c;数据作为一种共享资源&#xff0c;其并发访问的一致性和有效性是数据库必须解决的问题。锁机制通过对数据库中的数据对象&#xff08;如表、行等&#xff09;进行加锁&#xff0c;以确保在同…

基于python的去除图像内部填充

1 代码功能 该代码实现了一个图像处理的功能&#xff0c;具体来说是去除图像内部填充&#xff08;或更准确地说&#xff0c;是提取并显示图像中轮廓的外围区域&#xff0c;而忽略内部填充&#xff09;。以下是该功能的详细步骤&#xff1a; 读取图像&#xff1a;使用cv2.imread…

AWS服务器购买:如何选择合适的AWS云服务器

在当今数字化时代,云计算已成为企业IT基础设施的重要组成部分。作为全球领先的云服务提供商之一,亚马逊网络服务(AWS)提供了丰富多样的云服务器选项。然而,面对众多选择,如何为您的业务需求挑选最合适的AWS云服务器呢?我们结合九河云的分析来给你解答。 1. 明确业务需求 首先…

JVM调优:根据JVM自带工具定位问题(jps、jstat、Visual VM的使用)

JVM调优步骤 发现问题、定位问题、解决问题 发现问题 常见问题如下 GC频繁CPU负载过高内存溢出&#xff08;OOM&#xff09;内存泄露死锁程序响应时间较长 用JDK自带命令调优工具定位问题 jps&#xff08;java process status&#xff09;:查看正在运行的Java进程 基本语…

JUnit 单元测试

JUnit 测试是程序员测试&#xff0c;就是白盒测试&#xff0c;可以让程序员知道被测试的软件如何 &#xff08;How&#xff09;完成功能和完成什么样&#xff08;What&#xff09;的功能。 下载junit-4.12和hamcrest-core-1.3依赖包 相关链接 junit-4.12&#xff1a;Central …

html+canvas 实现签名功能-手机触摸

手机上的效果图 需要注意&#xff0c;手机触摸和鼠标不是一个事件&#xff0c;不能通用&#xff0c;上一篇是关于使用鼠标的样例 相关代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewpo…

什么是AQS(抽象队列同步器)?

AQS是AbstractQueuedSynchronizer的简称&#xff0c;即抽象队列同步器&#xff0c;从字面上可以这样理解: 抽象&#xff1a;抽象类&#xff0c;只实现一些主要逻辑&#xff0c;有些方法由子类实现&#xff1b;队列&#xff1a;使用先进先出&#xff08;FIFO&#xff09;的队列…

独立站外链如何影响搜索引擎排名?

独立站的外链对搜索引擎排名有着非常重要的影响。简单来说&#xff0c;外链就像是别的网站对你的网站投的信任票。每一条外链都告诉搜索引擎&#xff1a;“这个网站的内容是有价值的&#xff0c;值得推荐。”因此&#xff0c;外链的数量和质量直接影响你的网站在搜索引擎中的排…