Spring技术内幕笔记之SpringMvc

WebApplicationContext接口的类继承关系

org.springframework.web.context.ContextLoader#initWebApplicationContext 对IOC容器的初始化

SpringMvc如何设计

DispatcherServlet类继承关系

MVC处理流程图如下:

DispatcherServlet的工作大致可以分为两个部分:

  1. 初始化部分,由initServletBean()启动,通过initWebApplicationContext()方法最终调用DispatcherServlet的initStrategies方法,在这个方法里,DispatcherServlet对MVC模块的其他部分进行了初始化,比如handlerMapping、ViewResolver等;

  2. 对HTTP请求进行响应,作为一个Servlet,Web容器会调用Servlet的doGet()和doPost()方法,在经过FrameworkServlet的processRequest()简单处理后,会调用DispatcherServlet的doService()方法,在这个方法调用中封装了doDispatch(),这个doDispatch()是Dispatcher实现MVC模式的主要部分,会在下面进行详细的分析。

DispatcherServlet 启动与初始化

  1. Servlet初始化时org.apache.catalina.core.StandardWrapper#allocate,判断StandardWrapper实例是否存在(instanceInitialized状态标识),不存在则调用org.apache.catalina.core.StandardWrapper#initServlet方法进行初始化,然后调用javax.servlet.GenericServlet#init(javax.servlet.ServletConfig)方法,最终调用org.springframework.web.servlet.HttpServletBean#init方法
  2. init方法则调用org.springframework.web.servlet.HttpServletBean#init
    public final void init() throws ServletException {// Set bean properties from init parameters.PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);if (!pvs.isEmpty()) {try {BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));initBeanWrapper(bw);bw.setPropertyValues(pvs, true);}catch (BeansException ex) {if (logger.isErrorEnabled()) {logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);}throw ex;}}// Let subclasses do whatever initialization they like.initServletBean();}
    
  3. 可以看到最终会调用initServletBean方法,初始化实现是在org.springframework.web.servlet.FrameworkServlet#initServletBean
    protected final void initServletBean() throws ServletException {getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");if (logger.isInfoEnabled()) {logger.info("Initializing Servlet '" + getServletName() + "'");}long startTime = System.currentTimeMillis();try {this.webApplicationContext = initWebApplicationContext();initFrameworkServlet();}catch (ServletException | RuntimeException ex) {logger.error("Context initialization failed", ex);throw ex;}if (logger.isDebugEnabled()) {String value = this.enableLoggingRequestDetails ?"shown which may lead to unsafe logging of potentially sensitive data" :"masked to prevent unsafe logging of potentially sensitive data";logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +"': request parameters and headers will be " + value);}if (logger.isInfoEnabled()) {logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");}}
    
  4. 初始化Web容器是在initWebApplicationContext方法中,主要实现在org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext
    protected WebApplicationContext initWebApplicationContext() {WebApplicationContext rootContext =WebApplicationContextUtils.getWebApplicationContext(getServletContext());WebApplicationContext wac = null;if (this.webApplicationContext != null) {// A context instance was injected at construction time -> use itwac = this.webApplicationContext;if (wac instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;if (!cwac.isActive()) {// The context has not yet been refreshed -> provide services such as// setting the parent context, setting the application context id, etcif (cwac.getParent() == null) {// The context instance was injected without an explicit parent -> set// the root application context (if any; may be null) as the parentcwac.setParent(rootContext);}configureAndRefreshWebApplicationContext(cwac);}}}if (wac == null) {// No context instance was injected at construction time -> see if one// has been registered in the servlet context. If one exists, it is assumed// that the parent context (if any) has already been set and that the// user has performed any initialization such as setting the context idwac = findWebApplicationContext();}if (wac == null) {// No context instance is defined for this servlet -> create a local onewac = createWebApplicationContext(rootContext);}if (!this.refreshEventReceived) {// Either the context is not a ConfigurableApplicationContext with refresh// support or the context injected at construction time had already been// refreshed -> trigger initial onRefresh manually here.synchronized (this.onRefreshMonitor) {onRefresh(wac);}}if (this.publishContext) {// Publish the context as a servlet context attribute.String attrName = getServletContextAttributeName();getServletContext().setAttribute(attrName, wac);}return wac;}
    
  5. 可看到创建Web容器是在createWebApplicationContext方法中,主要实现在org.springframework.web.servlet.FrameworkServlet#createWebApplicationContext(org.springframework.context.ApplicationContext)
    protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {Class<?> contextClass = getContextClass();if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {throw new ApplicationContextException("Fatal initialization error in servlet with name '" + getServletName() +"': custom WebApplicationContext class [" + contextClass.getName() +"] is not of type ConfigurableWebApplicationContext");}ConfigurableWebApplicationContext wac =(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);wac.setEnvironment(getEnvironment());wac.setParent(parent);String configLocation = getContextConfigLocation();if (configLocation != null) {wac.setConfigLocation(configLocation);}configureAndRefreshWebApplicationContext(wac);return wac;}
    
  6. 此时就创建成功了Web容器 ConfigurableWebApplicationContext,然后调用配置启动并初始化容器,调用configureAndRefreshWebApplicationContext方法,代码如下:
    protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {if (ObjectUtils.identityToString(wac).equals(wac.getId())) {// The application context id is still set to its original default value// -> assign a more useful id based on available informationif (this.contextId != null) {wac.setId(this.contextId);}else {// Generate default id...wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());}}wac.setServletContext(getServletContext());wac.setServletConfig(getServletConfig());wac.setNamespace(getNamespace());wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));// The wac environment's #initPropertySources will be called in any case when the context// is refreshed; do it eagerly here to ensure servlet property sources are in place for// use in any post-processing or initialization that occurs below prior to #refreshConfigurableEnvironment env = wac.getEnvironment();if (env instanceof ConfigurableWebEnvironment) {((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());}postProcessWebApplicationContext(wac);applyInitializers(wac);wac.refresh();}
    
  7. 可以看到最终调用了org.springframework.context.support.AbstractApplicationContext#refresh方法初始化容器。

可看到创建Web容器时,设置了父容器,DispatcherServlet持有一个以自己的Servlet名称命名的IOC容器。

SpringMvc 路径与HandlerMethod初始化

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping的子类实现了InitializingBean接口,就必须实现afterPropertiesSet方法,主要实现如下:

public void afterPropertiesSet() {this.config = new RequestMappingInfo.BuilderConfiguration();this.config.setTrailingSlashMatch(useTrailingSlashMatch());this.config.setContentNegotiationManager(getContentNegotiationManager());if (getPatternParser() != null) {this.config.setPatternParser(getPatternParser());Assert.isTrue(!this.useSuffixPatternMatch && !this.useRegisteredSuffixPatternMatch,"Suffix pattern matching not supported with PathPatternParser.");}else {this.config.setSuffixPatternMatch(useSuffixPatternMatch());this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch());this.config.setPathMatcher(getPathMatcher());}super.afterPropertiesSet();}

可看到除了针对config做了一些初始设置操作外,最终调用了父类的afterPropertiesSet()实现,如下:

@Overridepublic void afterPropertiesSet() {initHandlerMethods();}

最终调用org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#initHandlerMethods方法,如下:

protected void initHandlerMethods() {for (String beanName : getCandidateBeanNames()) {if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {processCandidateBean(beanName);}}handlerMethodsInitialized(getHandlerMethods());}

可看到关键方法org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#processCandidateBean

protected void processCandidateBean(String beanName) {Class<?> beanType = null;try {beanType = obtainApplicationContext().getType(beanName);}catch (Throwable ex) {// An unresolvable bean type, probably from a lazy bean - let's ignore it.if (logger.isTraceEnabled()) {logger.trace("Could not resolve type for bean '" + beanName + "'", ex);}}if (beanType != null && isHandler(beanType)) {detectHandlerMethods(beanName);}}

此处调用了isHandler方法进行过滤,排除没有Controller或者RequestMapping注解Bean,实现如下:

@Overrideprotected boolean isHandler(Class<?> beanType) {return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));}

然后调用org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#detectHandlerMethods方法,代码如下:

protected void detectHandlerMethods(Object handler) {Class<?> handlerType = (handler instanceof String ?obtainApplicationContext().getType((String) handler) : handler.getClass());if (handlerType != null) {Class<?> userType = ClassUtils.getUserClass(handlerType);Map<Method, T> methods = MethodIntrospector.selectMethods(userType,(MethodIntrospector.MetadataLookup<T>) method -> {try {return getMappingForMethod(method, userType);}catch (Throwable ex) {throw new IllegalStateException("Invalid mapping on handler class [" +userType.getName() + "]: " + method, ex);}});if (logger.isTraceEnabled()) {logger.trace(formatMappings(userType, methods));}else if (mappingsLogger.isDebugEnabled()) {mappingsLogger.debug(formatMappings(userType, methods));}methods.forEach((method, mapping) -> {Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);registerHandlerMethod(handler, invocableMethod, mapping);});}}

此处关键方法,org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#registerHandlerMethod,实现如下:

protected void registerHandlerMethod(Object handler, Method method, T mapping) {this.mappingRegistry.register(mapping, handler, method);}

进入org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#register,进行注册,代码如下:

public void register(T mapping, Object handler, Method method) {this.readWriteLock.writeLock().lock();try {HandlerMethod handlerMethod = createHandlerMethod(handler, method);validateMethodMapping(handlerMethod, mapping);Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);for (String path : directPaths) {this.pathLookup.add(path, mapping);}String name = null;if (getNamingStrategy() != null) {name = getNamingStrategy().getName(handlerMethod, mapping);addMappingName(name, handlerMethod);}CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);if (corsConfig != null) {corsConfig.validateAllowCredentials();this.corsLookup.put(handlerMethod, corsConfig);}this.registry.put(mapping,new MappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig != null));}finally {this.readWriteLock.writeLock().unlock();}}

可看到registry将mapping添加成功,根据前面的rg.springframework.web.servlet.handler.AbstractHandlerMethodMapping#initHandlerMethods循环调用,会将所有的Controller里面的路径与MappingRegistration添加至Map<T, MappingRegistration> registry中。效果如下:

至此,Spring完成了所有的Controller的路径注册过程。

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

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

相关文章

NFC物联网开发智能衣橱解决方案

智能衣橱是智能家居的重要内容&#xff0c;现代家居市场对家居智能化控制尤为重视。但是&#xff0c;传统家居生产功能和模式已经无法满足智能化时代的需求&#xff0c;所以家居智能化成为家居行业发展的主要需求。与传统衣橱对比&#xff0c;智能衣橱的功能强大方便人们的生活…

Android--Jetpack--WorkManager详解

2024已经到来&#xff0c;愿你安睡时&#xff0c;山河入梦。愿你醒来时&#xff0c;满目春风。愿你欢笑时&#xff0c;始终如一。愿你行进时&#xff0c;前程似锦&#xff0c;坦荡从容。 编程语言的未来&#xff1f; 目录 一&#xff0c;定义 二&#xff0c;特点 三&#xff0c…

‘vue-cli-service‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件。这个问题如何解决?

这个错误信息 vue-cli-service 不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件 表示 vue-cli-service 命令在你的系统上未被识别。这通常是因为 Vue CLI 没有被正确安装或其路径没有被加入到系统的环境变量中。以下是几个解决这个问题的步骤&#xff1a; 确认 …

LinkedList与ArrayList的比较

1.LinkedList 基于双向链表&#xff0c;无需连续内存 随机访问慢&#xff08;要沿着链表遍历&#xff09; 头尾插入删除性能高 占用内存多 2.ArrayList 基于数组&#xff0c;需要连续内存 随机访问快&#xff08;指根据下标访问&#xff09; 尾部插入、删除性能可以&…

面试算法84:包含重复元素集合的全排列

题目 给定一个包含重复数字的集合&#xff0c;请找出它的所有全排列。例如&#xff0c;集合[1&#xff0c;1&#xff0c;2]有3个全排列&#xff0c;分别是[1&#xff0c;1&#xff0c;2]、[1&#xff0c;2&#xff0c;1]和[2&#xff0c;1&#xff0c;1]。 分析 下面采用回溯…

从0搭建github.io网页

点击跳转到&#x1f517;我的博客文章目录 从0搭建github.io网页 文章目录 从0搭建github.io网页1.成果展示1.1 网址和源码1.2 页面展示 2.new对象2.1 创建仓库 3.github.io仓库的初始化3.1 千里之行&#xff0c;始于足下3.2 _config.yml3.3 一点杂活 4.PerCheung.github.io.p…

如何为开源项目和社区做贡献 -- 你应该知道的十件事(四)——如何创建自己的开源项目?

问题四&#xff1a;如何创建自己的开源项目&#xff1f; 在前文中&#xff0c;我们已经介绍了最常见的给开源项目做贡献的两种途径&#xff0c;但随着你做开源项目的深入&#xff0c;你可能就会发现一个新的方向并推出自己的开源项目&#xff0c;下面&#xff0c;我将结合我的讲…

Linux 命令echo

命令作用 输出一行字符串在shell中&#xff0c;可以打印变量的值输出结果写入到文件在显示器上显示一段文字&#xff0c;起到提示的作用 语法 echo [选项] [字符串] 参数 字符含义-n不自动换行-e解释转义字符-E不解释转义字符 如果-e有效&#xff0c;则识别以下序列&…

SpringBoot 项目如何生成 swagger 文档

推荐使用 springdoc-openapi 的理由 1、springdoc-openapi 是 spring 官方出品&#xff0c;与 springboot 兼容更好&#xff08;springfox 兼容有坑&#xff09; 2、springdoc-openapi 社区更活跃&#xff0c;springfox 已经 2 年没更新了 3、springdoc-openapi 的注解更接近 …

力导向图与矩阵排序

Graph-layout force directed&#xff08;力导向图布局&#xff09;是一种用于可视化网络图的布局算法。它基于物理模型&#xff0c;模拟了图中节点之间的相互排斥和连接弹性&#xff0c;以生成具有良好可读性和美观性的图形布局。 在力导向图布局中&#xff0c;每个节点被视为…

(一)Matlab数值计算基础

目录 1.1Matlab命令组成 1.1.1基本符号 1.1.2功能符号 1.1.3常用命令 1.1Matlab命令组成 1.1.1基本符号 #提示运算符&#xff0c;表示软件处于准备就绪状态。在提示符号后输入一条命令或者一段程序后按Enter键&#xff0c;软件将给出相应的结果 >> *…

基于SpringBoot的影院订票系统

文章目录 项目介绍主要功能截图:部分代码展示设计总结项目获取方式🍅 作者主页:超级无敌暴龙战士塔塔开 🍅 简介:Java领域优质创作者🏆、 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系🍅 项目介绍 基于SpringBoot的影院订票系统,java项目…

牛客练习赛50-C

题目描述 在一个游戏中&#xff0c;tokitsukaze需要在n个士兵中选出一些士兵组成一个团去打副本。 第i个士兵的战力为v[i]&#xff0c;团的战力是团内所有士兵的战力之和。 但是这些士兵有特殊的要求&#xff1a;如果选了第i个士兵&#xff0c;这个士兵希望团的人数不超过s[i]…

【Proteus仿真】【Arduino单片机】汽车尾气检测报警系统

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真Arduino单片机控制器&#xff0c;使用按键、LCD1602液晶、蜂鸣器模块、CO、NOx、HC和PM2.5气体传感器等。 主要功能&#xff1a; 系统运行后&#xff0c;LCD1602显示CO、NOx、HC和…

手机录屏没有声音?让你的录屏有声有色

“有人知道手机录屏怎么录声音吗&#xff1f;今天录制了一个小时的直播视频&#xff0c;后面查看的时候发现没有声音&#xff0c;真的非常崩溃&#xff0c;想问问大家有没有办法&#xff0c;解决这个问题。” 在手机录屏的过程中&#xff0c;有时候我们可能会面临录制视频没有…

使用 Spectrum LSF 设置多集群和作业转发

使用 Spectrum LSF 设置多集群和作业转发 以下示例是有关如何使用 Spectrum LSF设置多集群和作业转发的指南。 此示例说明了集群是本地集群&#xff0c;另一个在云中的常见情况。 此示例假定标注为 “OnPremiseCluster” 的内部部署集群使用子网 192.168.0.0/24 &#xff0c;…

Spring技术内幕笔记之IOC的实现

IOC容器的实现 依赖反转&#xff1a; 依赖对象的获得被反转了&#xff0c;于是依赖反转更名为&#xff1a;依赖注入。许多应用都是由两个或者多个类通过彼此的合作来实现业务逻辑的&#xff0c;这使得每个对象都需要与其合作的对象的引用&#xff0c;如果这个获取过程需要自身…

奇因子之和(C语言)

题意&#xff1a; 一个整数的因子&#xff0c;就是所有可以整除这个数的数。奇数指在整数中&#xff0c;不能被 2 整除的数。所谓整数 Z 的奇因子&#xff0c;就是可以整除 Z 的奇数。 给定 N 个正整数&#xff0c;请你求出它们的第二大奇因子的和。当然&#xff0c;如果该数只…

Amazon API Gateway CORS 实战

Amazon API Gateway提供了一种实现跨域资源共享&#xff08;CORS&#xff09;的方式&#xff0c;以便在Web应用程序中安全地使用API。下面是Amazon API Gateway CORS的实战指南&#xff1a; 创建一个API Gateway REST API并定义资源和方法。在资源上启用CORS&#xff0c;可以通…

程序的重定位

可以理解为编译和链接 过程中产生的地址项都是临时的相对的。编译的时候的地址&#xff0c;在链接时会被修改。最终链接后生成的bin文件的地址项&#xff0c;在加载运行时 也会被修改。 链接器会对所有的输入文件进行扫描&#xff0c;之后就可以确定段的大小&#xff0c;符号定…