SpringMVC系列-1 使用方式和启动流程

news2024/11/18 4:51:48

背景

SpringMVC作为SSM组件之一,Java开发有必要了解SpringMVC是如何被集成到Spring框架以及整个项目的启动流程。本文以Tomcat作为Servlet容器进行介绍,默认认为读者使用过Tomcat且对Tomcat内部组件有足够的理解。

1.启动流程

当Tomcat被部署到服务器或者通过本地IDEA将项目war包通过local tomcat部署到Tomcat上后,可以通过startup.sh或者startup.bat触发Bootstrap的main方法,从而开启Tomcat容器组件的初始化和启动过程。从宏观上看,启动过程中对应着Listener-> Filter -> Servlet组件的触发流程。
本文涉及的组件包括Listener和Servlet:Tomcat在构造Context实例后会触发ServletContextEvent事件,通过ContextLoaderListener监听器触发Spring容器的创建和刷新过程;初始化Servlet时会进入DispatcherServlet的初始化方法,从而完成Spring MVC容器的创建和刷新过程。

2.使用方式

web.xml中常用的配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0">
	<!-- 指定Spring容器的配置文件 -->
	<context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:application-context.xml</param-value>
    </context-param>

	<!-- 指定Spring启动监听器 -->
	<listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>


	<!-- 配置DispatcherServlet -->
    <servlet>
        <servlet-name>SpringMVC</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

	<!-- 指定Spring MVC容器的配置文件 -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springMVC.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>SpringMVC</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

3.原理

3.1 Spring容器启动

当Tomcat启动时,通过ServletContextEvent事件进入ContextLoaderListener监听器中:

public void contextInitialized(ServletContextEvent event) {
    this.initWebApplicationContext(event.getServletContext());
}

通过event.getServletContext()可以获取ServletContext对象,该对象实际为ApplicationContextFacade类,该对象将作为整个项目的上下文对象。本质上是StandardContext对象的代理类(Tomcat创建的Context对象),代理关系如下所示:
在这里插入图片描述

initWebApplicationContext方法的主线逻辑如下所示:

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
	//⚠️1.创建ApplicationContext对象
   this.context = this.createWebApplicationContext(servletContext);
   ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;
   if (!cwac.isActive()) {
       if (cwac.getParent() == null) {
           ApplicationContext parent = this.loadParentContext(servletContext);
           cwac.setParent(parent);
       }
        //⚠️2.配置和刷新ApplicationContext
       this.configureAndRefreshWebApplicationContext(cwac, servletContext);
   }
   servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
   
   ClassLoader ccl = Thread.currentThread().getContextClassLoader();
   if (ccl == ContextLoader.class.getClassLoader()) {
       currentContext = this.context;
   } else if (ccl != null) {
       currentContextPerThread.put(ccl, this.context);
   }
   return this.context;
}

上述流程可以分为三部分:创建Spring容器、配置和刷新Spring容器、保存Spring容器信息至上下文。

3.1.1 创建Spring容器

跟进this.createWebApplicationContext(servletContext)方法:

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
     Class<?> contextClass = this.determineContextClass(sc);
     if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
         throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
     } else {
         return (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
     }
 }

逻辑较为简单,根据ServletContext上下文对象获取Spring容器类型,然后调用BeanUtils.instantiateClass方法通过反射构造Spring容器对象。
这里可以关注一下Spring容器的类型:

protected Class<?> determineContextClass(ServletContext servletContext) {
      String contextClassName = servletContext.getInitParameter("contextClass");
      if (contextClassName != null) {
          return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
      } else {
          contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
          return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
      }
  }

先从web.xml配置文件中的配置信息中获取,如果通过contextClass键指定了Spring容器类型则使用配置的类型,否则通过defaultStrategies.getProperty方法从ContextLoader.properties文件中读取,ContextLoader.properties文件内容如下:

org.springframework.web.context.WebApplicationContext=\
org.springframework.web.context.support.XmlWebApplicationContext

即Spring容器默认使用XmlWebApplicationContext类型。

3.1.2 配置和刷新Spring容器

configureAndRefreshWebApplicationContext方法完成了容器的刷新过程:

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
     wac.setServletContext(sc);
     String configLocationParam = sc.getInitParameter("contextConfigLocation");
     if (configLocationParam != null) {
         wac.setConfigLocation(configLocationParam);
     }

     ConfigurableEnvironment env = wac.getEnvironment();
     if (env instanceof ConfigurableWebEnvironment) {
         ((ConfigurableWebEnvironment)env).initPropertySources(sc, (ServletConfig)null);
     }

     this.customizeContext(sc, wac);
     wac.refresh();
 }

首先将上下文对象保存在Spring容器对象中;然后从web.xml配置信息中取出contextConfigLocation对应的文件来路径并将该路径设置给容器的configLocation属性,即为Spring容器指定了配置文件路径,此时可借助refreh()方法完成容器的刷新过程,该过程可参考Spring系列-1 启动流程。
在刷新容器之前,框架对环境变量的占位符做了替换处理(将环境变量中的占位符替换为真实的上下文对象)以及提供 customizeContext方法用于功能扩展。
在ContextLoaderListener监听器对象中,通过读取web.xml的contextInitializerClasses或者globalInitializerClasses属性信息收集ApplicationContextInitializer<ConfigurableApplicationContext>对象,并依次调用这些对象的initialize方。即,可以通过在web.xml中配置ApplicationContextInitializer实现类,实现容器刷新前的定制化操作(配置多个类时可以使用都好或者分号分割),如下所示:

public class SeongApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
        if (configurableApplicationContext instanceof XmlWebApplicationContext) {
            ((XmlWebApplicationContext)configurableApplicationContext).setAllowCircularReferences(true);
        }
    }
}

在配置文件中进行以下配置:

<context-param>
  <param-name>contextInitializerClasses</param-name>
  <param-value>SeongApplicationContextInitializer</param-value>
</context-param>

该案例实现了容器刷新前,强制设置容器支持循环依赖。

3.1.3 保存Spring容器信息至上下文

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

将Spring容器对象以org.springframework.web.context.WebApplicationContext.ROOT为key存放到上下文对象中。此时,上下文对象与Spring容器对象相互持有。

3.2 Spring MVC容器启动

Tomcat加载Servlet组件时,先实例化Servlet再调用Servlet的init方法。SpringMVC项目会在web.xml中配置的DispatcherServlet;而Spring MVC容器启动发生在DispatcherServlet的init方法中,跟随调用逻辑进入初始化方法(该方法定义在DispatcherServlet的父类HttpServletBean中):

public final void init() throws ServletException {
	//1.获取配置信息
	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) {
			throw ex;
		}
	}
	initServletBean();
}

上述逻辑分为两个步骤:从web.xml中获取配置信息并将所需的属性信息通过反射设置到DispatcherServlet属性中,如contextConfigLocation属性;然后调用initServletBean()方法完成SpringMVC容器的创建和刷新过程:

protected final void initServletBean() throws ServletException {
	this.webApplicationContext = initWebApplicationContext();
	initFrameworkServlet();
}

initServletBean()方法的主体逻辑在initWebApplicationContext()方法中实现,而initFrameworkServlet()作为扩展方法,此时方法体为空。
initWebApplicationContext()方法的主体逻辑如下:

protected WebApplicationContext initWebApplicationContext() {
	// ⚠️1.通过上下文获取Spring容器对象
	WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
	// ⚠️2.创建和刷新SpringMVC容器
	WebApplicationContext wac = createWebApplicationContext(rootContext);
	// ⚠️3.保存SpringMVC容器至上下文对象
	getServletContext().setAttribute("org.springframework.web.servlet.FrameworkServlet.CONTEXT.", wac);
	return wac;
}

3.2.1 通过上下文获取Spring容器对象

WebApplicationContextUtils.getWebApplicationContext(getServletContext())静态方法通过org.springframework.web.context.WebApplicationContext.ROOT键从ServletContext上下文对象中出Spring容器对象。

3.2.2 创建和刷新SpringMVC容器

在步骤3.2.1中获取了Spring容器对象,并通过参数传递给了createWebApplicationContext方法:

	protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
		Class<?> contextClass = getContextClass();
		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;
	}

在DispatcherServlet的父类FrameworkServlet中通过contextClass属性的默认值设定了SpringMVC默认的容器对象为XmlWebApplicationContext:

	private Class<?> contextClass = XmlWebApplicationContext.class;

通过getContextClass()方法得到XmlWebApplicationContext容器类型后,通过BeanUtils.instantiateClass(contextClass)反射创建容器对象。
得到容器对象后,构造环境对象并赋值、通过wac.setParent(parent)将Spring容器设置为该对象的父容器对象、设置configLocation属性(配置文件地址),然后调用configureAndRefreshWebApplicationContext初始化和刷新SpringMVC容器:

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
	wac.setServletContext(getServletContext());
	wac.setServletConfig(getServletConfig());
	wac.setNamespace(getNamespace());
	wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

	ConfigurableEnvironment env = wac.getEnvironment();
	if (env instanceof ConfigurableWebEnvironment) {
		((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
	}
	postProcessWebApplicationContext(wac);
	applyInitializers(wac);
	wac.refresh();
}

首先对容器的servletContext、servletConfig、namespace的属性进行设置;然后进行环境变量占位符的替换以及ApplicationContextInitializer—initialize的调用(同上述Spring容器);
postProcessWebApplicationContext(wac)方法为扩展方法,此时逻辑为空。

上述方法的核心逻辑在于wac.refresh(),完成SpringMVC容器的刷新,同Spring容器的刷新过程。

3.2.3 保存SpringMVC容器至上下文对象

getServletContext().setAttribute("org.springframework.web.servlet.FrameworkServlet.CONTEXT.", wac);

getServletContext()可以获取ServletContext上下文对象,以"org.springframework.web.servlet.FrameworkServlet.CONTEXT."为键将SpringMVC容器对象储存到上下文对象的属性中。

至此,SpringMVC项目的整理启动流程已介绍完毕。

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

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

相关文章

node的安装配置

这个是官网下载地址 推荐选择下载LTS版本的下载&#xff0c;不推荐下载最新版的&#xff0c;下载完成之后&#xff0c;在安装过程中更改安装路径默认是c盘的&#xff0c;如果c盘空间大当我没说&#xff0c;接着一路傻瓜式安装默认点击下一步&#xff0c;直到安装完成。 创建文…

chatgpt赋能python:Python怎样设置字体大小

Python 怎样设置字体大小 作为一名Python工程师&#xff0c;我们经常需要在论文或文档中使用Python进行数据分析&#xff0c;数据可视化和数据科学等方面的工作。在这些文档中&#xff0c;字体大小的设置是非常重要的&#xff0c;因为它直接影响阅读体验和文档的可读性。因此&…

幸运九宫格抽奖码九宫格抽奖系统独立版源码php修复版

&#x1f389; 有需要的朋友记得关赞评&#xff0c;文章底部来交流&#xff01;&#xff01;&#xff01; &#x1f389; ✨ 源码介绍 1.本程序在PHP5.3 ~ PHP5.6环境 2.后台访问地址&#xff1a;域名/admin/index.php U:admin P:admin 手动安装 1.导入1776.sql到数据库 …

【文件操作与IO】认识文件

目录 认识文件 狭义上的文件 广义上的文件 树形结构组织和目录 文件路径&#xff08;Path&#xff09; 其他知识 认识文件 狭义上的文件 硬盘上保存的数据&#xff0c;都是“文件”来组织的&#xff0c;本质上都是二进制或是字符组织的数组&#xff0c;被打包成一个文件…

Git学习笔记(上篇)

导航小助手 一、Git初识 1.1 场景 1.2 版本控制器 1.3 注意事项 二、Git安装 2.1 Linux-centos 2.2 Linux-ubuntu 三、Git基本操作 3.1 创建Git本地仓库 3.2 配置Git本地仓库 3.3 认识工作区、暂存区、版本库 3.3.1 添加文件—场景一 3.3.2 添加文件—场景二 3.4…

chatgpt赋能python:Python怎么重新执行代码

Python怎么重新执行代码 如果你正在学习编程&#xff0c;你可能已经意识到Python是一种非常流行的语言。Python是一种如此强大的编程语言&#xff0c;可以应用于许多领域&#xff0c;从数据分析到游戏开发&#xff0c;甚至是Web开发。但是&#xff0c;在编写Python代码时&…

026.【图形结构算法】

1. 图的定义 树形结构用于描述节点和节点之间的层次关系&#xff0c;而图形结构用于描述两个顶点之间是否连通的关系。在计算机科学中&#xff0c;图形结构是最灵活的数据结构之一&#xff0c;很多问题都可以使用图来求解。 无向图是每条边都没有方向的图&#xff0c;同一个边…

chatgpt赋能python:重新安装Python——让你的编程之路更畅通

重新安装Python——让你的编程之路更畅通 Python是一种高级编程语言&#xff0c;广泛应用于软件开发、数据科学、机器学习等领域&#xff0c;因其易学易用、拥有丰富的第三方库和社区支持而备受程序员们的喜爱。但是&#xff0c;有时候你可能会遇到无法解决的Python问题&#…

Linux 4.10当中将带来深远影响的三项小改变

Linux的演进永不停歇。Linus Torvalds一直在努力工作&#xff0c;希望能够在新的内核版本当中(4.11)融入更多变化。不过在目前的Linux 4.10中&#xff0c;我们同样发现了三组能够有效提升性能并实现多种前所未有功能集的变更。 下面&#xff0c;我们将共同了解这些可能对您、您…

0006-TIPS-2020-hxp-kernel-rop : bypass-KASLR-with-offset_leak

内核默认加载地址&#xff08;不开启KASLR&#xff09; kernel text mapping 在内核linux-5.9/Documentation/x86/x86_64/mm.rst文档中记录了 x86_64虚拟地址空间布局 其中0xffffffff80000000~0xffffffff9fffffff用于存放内核代码段、全局变量、BSS等 ffffffff80000000 | -…

华为OD机试真题 JavaScript 实现【字符串序列判定】【2022Q4 100分】,附详细解题思路

一、题目描述 输入两个字符串a和b&#xff0c;都只包含英文小写字母。a长度<100&#xff0c;b长度<500,000。 判定a是否是b的有效子串。 判定规则&#xff1a; a中的每个字符在b中都能找到&#xff08;可以不连续&#xff09;&#xff0c;且a在b中字符的前后顺序与a中…

【SQL应知应会】分析函数的点点滴滴(一)

欢迎来到爱书不爱输的程序猿的博客, 本博客致力于知识分享&#xff0c;与更多的人进行学习交流 本文收录于SQL应知应会专栏,本专栏主要用于记录对于数据库的一些学习&#xff0c;有基础也有进阶&#xff0c;有MySQL也有Oracle 分析函数的点点滴滴 1.什么是分析函数&#xff1a;…

万字详解常用设计模式

本文是博主在工作中对常用设计模式的使用经验总结归纳而来分享给大家。 设计模式一共有23种&#xff0c;本文讲解涉及如下&#xff1a; 责任链模式 模板方法模式 发布订阅模式 策略模式 三大分类 业界一般将设计模式分为三大类&#xff1a; 创建型模式&#xff1a;对类的实…

chatgpt赋能python:Python怎样调字体大小以及优化网站SEO

Python怎样调字体大小以及优化网站SEO 在现代网络时代&#xff0c;网站的排名和SEO越来越受到关注。有一些关键词和技巧可以用来在搜索引擎排名中获得好的位置。其中一个技术是调整字体大小。在本文中&#xff0c;我们将深入探讨如何使用Python调整字体大小&#xff0c;并进一…

chatgpt赋能python:Python如何随机产生多个随机数?

Python如何随机产生多个随机数&#xff1f; Python是一种高级编程语言&#xff0c;它的随机数生成器是其强大的功能之一。在本文中&#xff0c;我们将学习如何使用Python随机数生成器生成多个随机数。 基本概念&#xff1a;随机数生成器 随机数生成器是一种算法或物理设备&a…

【Python】APScheduler定时调度库

文章目录 APScheduler是什么功能特点四大组件触发器[triggers]date 一次性触发器interval 间隔触发器cron 周期触发器 任务存储器[JobStore]MemoryJobStoreMongoDBJobStoreRedisJobStore示例代码 RethinkDBJobStoreSQLAlchemyJobStoreZooKeeperJobStore 执行器[executors]线程池…

chatgpt赋能python:Python如何隐藏进程

Python如何隐藏进程 介绍 进程是指运行中的程序在操作系统中的一个实例。在计算机系统中&#xff0c;进程通常都可以被用户或者其他程序所看到。然而&#xff0c;有时候我们需要隐藏进程&#xff0c;比如保护敏感信息或者防止恶意攻击。 Python是一种高级编程语言&#xff0…

Maven学习笔记(SSM 整合伪分布式案例)

目录 第一节 创建工程&#xff0c;引入依赖 1 创建工程 ①工程清单 ②工程间关系 2、各工程 POM 配置 ①父工程 ②Mybatis 逆向工程 ③环境依赖工程 ④工具类工程 ⑤实体类工程 ⑥组件工程 ⑦Web 工程 第二节 搭建环境&#xff1a;持久化层 1、物理建模 2、Myba…

leetcode算法 -- 数组

1 数组 常见的数组算法有双指针&#xff0c;滑动窗口&#xff0c;二分查找和分冶。 2 双指针 核心的思路&#xff1a;使用两个指针&#xff0c;一个从头开始索引&#xff0c;一个从尾开始索引。 2.1 两数之和ii 167 给你一个下标从 1 开始的整数数组 numbers &#xff0c;该…

chatgpt赋能python:Python怎么随机生成一个数

Python怎么随机生成一个数 在Python编程中&#xff0c;经常有需要随机生成一个整数的需求&#xff0c;比如在游戏中生成随机的道具&#xff0c;或者在数据分析中进行随机采样。 Python中提供了一个内置的random模块&#xff0c;可以方便地实现随机生成一个数。 使用random模…