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

news/2024/10/30 23:17:23/

背景

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.配置和刷新ApplicationContextthis.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> {@Overridepublic 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.ppmy.cn/news/460216.html

相关文章

【AI绘画】精选XP列表展示,TAG分享

这里全是原图哦&#xff0c;想要无水印的话私发吧~&#xff0c;B站那边只能传缩略图了ヾ(•ω•)o {best quality}, {{masterpiece}}, {highres}, original, extremely detailed wallpaper, 1girlbest quality, masterpiece, highres, original, extremely detailed wallpaper…

【神器】Adobe Illustrator-作图利器

但我们却不加留意地度过我们美好的日子&#xff0c;只有到了糟糕的日子真正来临的时候&#xff0c;我们才会想念和渴望曾经有过的美好日子。我们脸带愁容&#xff0c;许多欢乐愉快的时光未加品尝和咀嚼就过去了&#xff0c;直到以后日子变得艰难和令人沮丧的时候&#xff0c;我…

AI在线画图(文生图,通过文字绘制图片)

介绍一款AI在线画图工具&#xff0c;如果有作图需求&#xff0c;并且网络上找不到自己满意的图片&#xff0c;来这里直接生成会很有意思。 https://wenxin.baidu.com/moduleApi/ernieVilg 这里只需要输入你想象的画面文字&#xff0c;即可在几分钟之内生成多个不同的图片。比如…

【AI 2021】Adobe Illustrator 2021 软件下载及安装教程

Adobe Illustrator 2021安装包 下载地址&#xff1a; https://pan.baidu.com/s/1BIi5_9s8ea1O4VZFHX3vfw 提取码&#xff1a;zyhh 下载方式&#xff1a; 复制链接到浏览器中打开&#xff0c;输入提取码&#xff0c;保存到网盘&#xff0c;然后打开百度网盘&#xff08;PC端…

AI(adobe illustrator)怎么设置导出图片的像素尺寸

1、打开 Adobe Illustrator CS5 。 2、执行【文件】-【新建】命令&#xff0c;新建一个图像&#xff0c;把图片的大小设定320x320。 3、在画布中用【椭圆工具】画两个圆。 4、分别将两个圆着色为红色和蓝色。 5、使用选择工具选中两个圆。 6、执行【窗口】-【路径查找器】命令&…

简单易用的AI桌面工具系列 - 图像生成

简单易用的AI桌面工具系列 图像生成 文生图&#xff1a;输入提示词&#xff0c;生成图片&#xff08;仅支持英文&#xff09;图生图&#xff1a;根据图片及提示词生成图片图像无损放大&#xff1a;比如将 512*512 放大到 1024 * 1024分辨率 512*512 25步 CPU(i5处理器) 5分钟…

Adobe illustrator2022(Ai2022)新增功能

① 使用 Adobe Substa-nce 材质添加纹理 使用 Substance 材质为图稿添加纹理&#xff0c;并创建逼真的 3D 图形。您可以添加自己的材质&#xff0c;也可以从数以千计的 Sub-stance 材质资源中进行选择。 ② 无缝激活缺失字体 现在&#xff0c;您可以任何计算机上加载文档并无…

Adobe Illustrator(Ai) 2020中文版

Adobe Illustrator(Ai) 2020中文版在功能上更加侧重于图像的勾画。所以这样看来它们之间的侧重点不同。但是相同的是&#xff0c;它们在矢量图的设计行业中&#xff0c;都是非常受用户欢迎的。如果您对于Photoshop这款软件的使用比较熟练的话&#xff0c;将可以更快地上手Ai这款…