spring启动流程 (6完结) springmvc启动流程

SpringMVC的启动入口在SpringServletContainerInitializer类,它是ServletContainerInitializer实现类(Servlet3.0新特性)。在实现方法中使用WebApplicationInitializer创建ApplicationContext、创建注册DispatcherServlet、初始化ApplicationContext等。

SpringMVC已经将大部分的启动逻辑封装在了几个抽象WebApplicationInitializer中,开发者只要继承这些抽象类实现抽象方法即可。

本文将详细分析ServletContainerInitializer、SpringServletContainerInitializer和WebApplicationInitializer的工作流程。

Servlet3.0的ServletContainerInitializer

ServletContainerInitializer接口

ServletContainerInitializer是Servlet3.0的接口。

该接口在web应用程序启动阶段接收通知,注册servlet、filter、listener等。

该接口的实现类可以用HandlesTypes进行标注,并指定一个Class值,后续会将实现、扩展了这个Class的类集合作为参数传递给onStartup方法。

以tomcat为例,在容器配置初始化阶段,将使用SPI查找实现类,在ServletContext启动阶段,初始化并调用onStartup方法来进行ServletContext的初始化。

SpringServletContainerInitializer实现类

在onStartup方法创建所有的WebApplicationInitializer对并调用onStartup方法。

以下为SPI配置,在spring-web/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer文件:

在这里插入图片描述

WebApplicationInitializer接口

WebApplicationInitializer接口概述

Interface to be implemented in Servlet 3.0+ environments in order to configure the ServletContext programmatically – as opposed to (or possibly in conjunction with) the traditional web.xml-based approach.

Implementations of this SPI will be detected automatically by SpringServletContainerInitializer, which itself is bootstrapped automatically by any Servlet 3.0 container. See its Javadoc for details on this bootstrapping mechanism.

示例:

public class MyWebAppInitializer implements WebApplicationInitializer {

   @Override
   public void onStartup(ServletContext container) {
     // Create the 'root' Spring application context
     AnnotationConfigWebApplicationContext rootContext =
       new AnnotationConfigWebApplicationContext();
     rootContext.register(AppConfig.class);

     // Manage the lifecycle of the root application context
     container.addListener(new ContextLoaderListener(rootContext));

     // Create the dispatcher servlet's Spring application context
     AnnotationConfigWebApplicationContext dispatcherContext =
       new AnnotationConfigWebApplicationContext();
     dispatcherContext.register(DispatcherConfig.class);

     // Register and map the dispatcher servlet
     ServletRegistration.Dynamic dispatcher =
       container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
     dispatcher.setLoadOnStartup(1);
     dispatcher.addMapping("/");
   }
}

开发者可以编写类继承AbstractAnnotationConfigDispatcherServletInitializer抽象类,这个抽象类已经将大部分的初始化逻辑进行了封装。

WebApplicationInitializer实现和继承关系

在这里插入图片描述

AbstractContextLoaderInitializer类:

Convenient base class for WebApplicationInitializer implementations that register a ContextLoaderListener in the servlet context. The only method required to be implemented by subclasses is createRootApplicationContext(), which gets invoked from registerContextLoaderListener(ServletContext).

AbstractDispatcherServletInitializer类:

Base class for WebApplicationInitializer implementations that register a DispatcherServlet in the servlet context. Most applications should consider extending the Spring Java config subclass AbstractAnnotationConfigDispatcherServletInitializer.

AbstractAnnotationConfigDispatcherServletInitializer类:

WebApplicationInitializer to register a DispatcherServlet and use Java-based Spring configuration.
Implementations are required to implement:

  • getRootConfigClasses() – for “root” application context (non-web infrastructure) configuration.
  • getServletConfigClasses() – for DispatcherServlet application context (Spring MVC infrastructure) configuration.

If an application context hierarchy is not required, applications may return all configuration via getRootConfigClasses() and return null from getServletConfigClasses().

开发者SpringMvcInitializer示例

开发者需要编写类继承AbstractAnnotationConfigDispatcherServletInitializer类,实现几个抽象方法:

public class SpringMvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

  /**
   * 指定创建root application context需要的@Configuration/@Component类
   */
  @Override
  protected Class<?>[] getRootConfigClasses() {
    return new Class[]{AppConfig.class};
  }

  /**
   * 指定创建Servlet application context需要的@Configuration/@Component类
   * 如果所有的配置类都使用root config classes就返回null
   */
  @Override
  protected Class<?>[] getServletConfigClasses() {
    return null;
  }

  /**
   * Specify the servlet mapping(s) for the DispatcherServlet — for example "/", "/app", etc.
   */
  @Override
  protected String[] getServletMappings() {
    return new String[]{"/"};
  }
}

SpringMVC启动流程

入口AbstractDispatcherServletInitializer.onStartup方法

public void onStartup(ServletContext servletContext) throws ServletException {
	super.onStartup(servletContext);
	registerDispatcherServlet(servletContext);
}

父类的onStartup方法创建RootApplicationContext、注册ContextLoaderListener监听器:

public void onStartup(ServletContext servletContext) throws ServletException {
	registerContextLoaderListener(servletContext);
}

protected void registerContextLoaderListener(ServletContext servletContext) {
	WebApplicationContext rootAppContext = createRootApplicationContext();
	if (rootAppContext != null) {
		// ContextLoaderListener是ServletContextListener
		// 会在contextInitialized阶段初始化RootApplicationContext
		ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
		listener.setContextInitializers(getRootApplicationContextInitializers());
		servletContext.addListener(listener);
	}
}

注册DispatcherServlet

registerDispatcherServlet方法用于创建ServletApplicationContext、注册DispatcherServlet:

protected void registerDispatcherServlet(ServletContext servletContext) {
	String servletName = getServletName();

	// 创建ServletApplicationContext
	WebApplicationContext servletAppContext = createServletApplicationContext();

	// 创建DispatcherServlet
	FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
	// 添加ApplicationContextInitializer集,会在初始化时调用
	dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

	// 添加到ServletContext
	ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);

	registration.setLoadOnStartup(1);
	registration.addMapping(getServletMappings());
	registration.setAsyncSupported(isAsyncSupported());

	Filter[] filters = getServletFilters();
	if (!ObjectUtils.isEmpty(filters)) {
		for (Filter filter : filters) {
			registerServletFilter(servletContext, filter);
		}
	}

	customizeRegistration(registration);
}

触发ContextLoaderListener监听器contextInitialized事件

这个是Servlet的ServletContextListener机制,在ServletContext创建之后触发contextInitialized事件:

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

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
	// 判断是否已经在当前ServletContext绑定了WebApplicationContext
	// 如果已经绑定抛错
	if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
		throw new IllegalStateException("已经初始化过了");
	}

	try {
		if (this.context == null) {
			this.context = createWebApplicationContext(servletContext);
		}
		if (this.context instanceof ConfigurableWebApplicationContext) {
			ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
			if (!cwac.isActive()) {
				if (cwac.getParent() == null) {
					ApplicationContext parent = loadParentContext(servletContext);
					cwac.setParent(parent);
				}
				// refresh
				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;
	} catch (RuntimeException | Error ex) {
		servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
		throw ex;
	}
}

protected void configureAndRefreshWebApplicationContext(
		ConfigurableWebApplicationContext wac, ServletContext sc) {
	// 给wac设置id

	wac.setServletContext(sc);
	// 设置spring主配置文件路径
	String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
	if (configLocationParam != null) {
		wac.setConfigLocation(configLocationParam);
	}

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

	customizeContext(sc, wac);
	// 刷新ApplicationContext
	wac.refresh();
}

触发DispatcherServlet的init事件

Servlet在接收请求之前会调用其init方法进行初始化,这个是Servlet的规范。

init方法在其父类HttpServletBean中:

public final void init() throws ServletException {

	// 从ServletConfig获取配置设置到Servlet
	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();
}

// FrameworkServlet
protected final void initServletBean() throws ServletException {
	try {
		// 初始化ServletApplicationContext
		this.webApplicationContext = initWebApplicationContext();
		initFrameworkServlet();
	} catch (ServletException | RuntimeException ex) {
		throw ex;
	}
}

protected WebApplicationContext initWebApplicationContext() {
	// 获取rootApplicationContext
	// 之前的ServletContext初始化阶段已经绑定
	WebApplicationContext rootContext =
			WebApplicationContextUtils.getWebApplicationContext(getServletContext());
	WebApplicationContext wac = null;

	if (this.webApplicationContext != null) {
		wac = this.webApplicationContext;
		if (wac instanceof ConfigurableWebApplicationContext) {
			ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
			if (!cwac.isActive()) {
				// 将rootApplicationContext设置为Parent
				if (cwac.getParent() == null) {
					cwac.setParent(rootContext);
				}
				// 刷新ApplicationContext
				configureAndRefreshWebApplicationContext(cwac);
			}
		}
	}
	// 如果没有需要查找或创建
	if (wac == null) {
		wac = findWebApplicationContext();
	}
	if (wac == null) {
		wac = createWebApplicationContext(rootContext);
	}

	if (!this.refreshEventReceived) {
		synchronized (this.onRefreshMonitor) {
			// 子类实现
			onRefresh(wac);
		}
	}

	if (this.publishContext) {
		String attrName = getServletContextAttributeName();
		getServletContext().setAttribute(attrName, wac);
	}

	return wac;
}

// DispatcherServlet
protected void onRefresh(ApplicationContext context) {
	initStrategies(context);
}

// 初始化SpringMVC相关组件
protected void initStrategies(ApplicationContext context) {
	initMultipartResolver(context);
	initLocaleResolver(context);
	initThemeResolver(context);
	initHandlerMappings(context);
	initHandlerAdapters(context);
	initHandlerExceptionResolvers(context);
	initRequestToViewNameTranslator(context);
	initViewResolvers(context);
	initFlashMapManager(context);
}

SpringMVC启动流程总结

  • 创建RootApplicationContext、注册ContextLoaderListener监听器
  • 创建ServletApplicationContext、注册DispatcherServlet
  • 触发ContextLoaderListener监听器contextInitialized事件,初始化RootApplicationContext
  • 触发DispatcherServlet的init事件,初始化ServletApplicationContext

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Selenium多浏览器处理

Python 版本 #导入依赖 import os from selenium import webdriverdef test_browser():#使用os模块的getenv方法来获取声明环境变量browserbrowser os.getenv("browser").lower()#判断browser的值if browser "headless":driver webdriver.PhantomJS()e…

【多模态】19、RegionCLIP | 基于 Region 来实现视觉语言模型预训练

文章目录 一、背景二、方法2.1 Region-based Language-Image Pretraining2.2 目标检测的迁移学习 三、效果3.1 数据集3.2 实现细节3.3 结果 论文&#xff1a; RegionCLIP: Region-based Language-Image Pretraining 代码&#xff1a;https://github.com/microsoft/RegionCLIP …

手写线程池 - C++版 - 笔记总结

1.线程池原理 创建一个线程&#xff0c;实现很方便。 缺点&#xff1a;若并发的线程数量很多&#xff0c;并且每个线程都是执行一个时间较短的任务就结束了。 由于频繁的创建线程和销毁线程需要时间&#xff0c;这样的频繁创建线程会大大降低 系统的效率。 2.思考 …

Maven基础之项目创建、packaging

文章目录 创建 maven 项目流程骨架是浮云&#xff0c;packaging 是关键 创建 maven 项目流程 通过骨架&#xff08;archetype&#xff09;创建 maven 工程 第一步&#xff1a;选择 new → maven → Maven Project 第二步&#xff1a;New Maven Project 窗口不作任何设置&…

Zabbix监控ActiveMQ

当我们在线上使用了ActiveMQ 后&#xff0c;我们需要对一些参数进行监控&#xff0c;比如 消息是否有阻塞&#xff0c;哪个消息队列阻塞了&#xff0c;总的消息数是多少等等。下面我们就通过 Zabbix 结合 Python 脚本来实现对 ActiveMQ的监控。 一、创建 Activemq Python 监控…

Java 异常处理的使用和思考

概念 异常处理的概念起源于早期的编程语言&#xff0c;如 LISP、PL/I 和 CLU。这些编程语言首次引入了异常处理机制&#xff0c;以便在程序执行过程中检测和处理错误情况。异常处理机制随后在 Ada、Modula-3、C、Python、Java 等编程语言中得到了广泛采用和发展。在 Java 中&a…

Unity Addressable

Unity重要目录 工程中的几个重要目录 Assets存放资源、代码、配置Library大部分的资源导入到Assets目录之后&#xff0c;会转化成Unity认可的文件&#xff0c;转化后的文件会存储在这个目录Logs日志文件Packages第三方插件ProjectSettings存放各种项目设定UserSettings用户偏好…

基于Truss+Docker+Kubernetes把开源模型Falcon-7B送上云端(译)

背景 到目前为止&#xff0c;我们已经看到了ChatGPT的能力及其所能提供的强大功能。然而&#xff0c;对于企业应用来说&#xff0c;像ChatGPT这样的封闭源代码模型可能会带来风险&#xff0c;因为企业自身无法控制他们的数据。尽管OpenAI公司声称用户数据不会被存储或用于训练…

python 将pdf文件转图片

有小伙伴问了怎么将 pdf文件转图片的问题&#xff0c;我百度了一波儿&#xff0c;搞了以下python代码给他封装成exe工具了。 中途打包踩了个坑&#xff0c;python进程池的问题&#xff0c;本地运行没啥问题&#xff0c;打包好的exe文件双击就会使电脑内存爆破卡死&#xff0c;…

缩略所写的代码

有一长串的代码需要进行缩略 可以在要缩略的代码的前一行加上注释。并在其中写上 #region。 在最后一行的下一行加上注释&#xff0c;并在其中写上 #endregion。 最终结果&#xff1a;

神经数据库:用于使用 ChatGPT 构建专用 AI 代理的下一代上下文检索系统 — (第 2/3 部分)

书接上回理解构建LLM驱动的聊天机器人时的向量数据库检索的局限性 - &#xff08;第1/3部分&#xff09;_阿尔法旺旺的博客-CSDN博客 其中我们强调了&#xff08;1&#xff09;嵌入生成&#xff0c;然后&#xff08;2&#xff09;使用近似近邻&#xff08;ANN&#xff09;搜索…

25.9 matlab里面的10中优化方法介绍—— 惩罚函数法求约束最优化问题(matlab程序)

1.简述 一、算法原理 1、问题引入 之前我们了解过的算法大部分都是无约束优化问题&#xff0c;其算法有&#xff1a;黄金分割法&#xff0c;牛顿法&#xff0c;拟牛顿法&#xff0c;共轭梯度法&#xff0c;单纯性法等。但在实际工程问题中&#xff0c;大多数优化问题都属于有约…

AI帮你制作海报

介绍 Microsoft Designer是由微软推出的图像处理软件&#xff0c;能够通过套用模板等方式快速完成设计加工&#xff0c;生成能够在社交媒体使用的图片。Designer的使用更为简单便捷&#xff0c;用户能够通过套用模板等方式快速完成设计加工&#xff0c;生成能够在社交媒体使用…

【计算机网络】应用层协议 -- HTTP协议

文章目录 1. 认识HTTP协议2. 认识URL3. HTTP协议格式3.1 HTTP请求协议格式3.2 HTTP响应协议格式 4. HTTP的方法5. HTTP的状态码6. HTTP的Header7. Cookie和Session 1. 认识HTTP协议 协议。网络协议的简称&#xff0c;网络协议是通信计算机双方必须共同遵守的一组约定&#xff0…

Rust vs Go:常用语法对比(五)

题图来自 Rust vs Go 2023[1] 81. Round floating point number to integer Declare integer y and initialize it with the rounded value of floating point number x . Ties (when the fractional part of x is exactly .5) must be rounded up (to positive infinity). 按规…

oCPC实践录 | oCPC下机制设计变得毫无意义?(2)无声的战争

接上回oCPC实践录 | oCPC下机制设计变得毫无意义&#xff1f;&#xff08;1&#xff09;事出异常必有妖&#xff0c;互联网广告最开始采用的广义第一价格密封拍卖&#xff08;GFP)&#xff0c;对广告主而言&#xff0c;需要不断感知竞争对手的变化&#xff0c;修改报价&#xf…

BIOS相关知识

简介 BIOS&#xff08;Basic Input Output System&#xff09;基本输入输出系统&#xff0c;固化在服务器主板的专用ROM中&#xff0c;是加载在服务器硬件系统上最基本的运行程序。BIOS位于硬件和系统中间&#xff0c;用来初始化硬件&#xff0c;为操作系统运行做准备 功能 …

ES6 - promise.all和race方法的用法详解

文章目录 一、前言二、Promise.all()1&#xff0c;第一句&#xff1a;Promise.all()方法接受一个数组作为参数&#xff0c;且每一个都是 Promise 实例2&#xff0c;第二句&#xff1a;如果不是&#xff0c;就会先调Promise.resolve方法&#xff0c;将参数转为 Promise 实例再进…

shell脚本:数据库的分库分表

#!/bin/bash ######################### #File name:db_fen.sh #Version:v1.0 #Email:admintest.com #Created time:2023-07-29 09:18:52 #Description: ########################## MySQL连接信息 db_user"root" db_password"RedHat123" db_cmd"-u${…

c语言位段知识详解

本篇文章带来位段相关知识详细讲解&#xff01; 如果您觉得文章不错&#xff0c;期待你的一键三连哦&#xff0c;你的鼓励是我创作的动力之源&#xff0c;让我们一起加油&#xff0c;一起奔跑&#xff0c;让我们顶峰相见&#xff01;&#xff01;&#xff01; 目录 一.什么是…