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

news2025/1/12 12:22:54

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

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

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

相关文章

大数据Flink(五十):流式计算简介

文章目录 流式计算简介 一、数据的时效性 二、流式计算和批量计算

dubbo概论

目录 前言&#xff1a; 1.概述 2.服务发现 3.容错 3.1.注册中心宕机 3.2.负载均衡 3.3.限流、熔断 4.通信协议 4.1.HTTP 4.2.dubbo 5.总结 前言&#xff1a; 这可能是全网最深入浅出的dubbo文章&#xff0c;如果你会spring cloud&#xff0c;恭喜你&#xff0c;你可…

Zookeeper命令总结

目录 1、常用命令2、ls path3、create xxx创建持久化节点创建临时节点创建持久化序列节点 4、get path5、set path6、delete path7、监听器总结1&#xff09;节点的值变化监听2&#xff09;节点的子节点变化监听&#xff08;路径变化&#xff09;3&#xff09;当某个节点创建或…

Maven基础之项目创建、packaging

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

【软件测试】基于博客系统的自动化测试

目录 1.我的博客系统链接 2.使用selenium对博客系统进行自动化测试 1.引入依赖 2.创建公共类 3.创建测试套件类 4.测试登陆界面 5. 测试博客列表页 6.测试写博客页面 7.测试删除博客 8.最终运行结果 1.我的博客系统链接 用户登录 2.使用selenium对博客系统进行自动…

网络安全(黑客)系统自学,成为一名白帽黑客

前言 黑客技能是一项非常复杂和专业的技能&#xff0c;需要广泛的计算机知识和网络安全知识。你可以参考下面一些学习步骤&#xff0c;系统自学网络安全。 在学习之前&#xff0c;要给自己定一个目标或者思考一下要达到一个什么样的水平&#xff0c;是学完找工作&#xff08;…

学习笔记22 map

一、概论 map的每个元素都由两个部分组成&#xff1a;键和值。每个键都与一个特定的值相关联&#xff0c;并且可以用于定位该值。 map和set很像&#xff0c;只不过map存储的是key&#xff0c;由key再映射到value&#xff0c;而set存储的就是value本身。 Map接口是泛型的&…

拦截Bean使用之前各个时机的Spring组件

拦截Bean使用之前各个时机的Spring组件 之前使用过的BeanPostProcessor就是在Bean实例化之后&#xff0c;注入属性值之前的时机。 Spring Bean的生命周期本次演示的是在Bean实例化之前的时机&#xff0c;使用BeanFactoryPostProcessor进行验证&#xff0c;以及在加载Bean之前进…

flink to starrocks 问题集锦....

[问题排查]导入失败相关 - 问题排查 - StarRocks中文社区论坛 starrocks官网如下&#xff1a; Search StarRocks Docs starrocks内存配置项&#xff1a; 管理内存 Memory_management StarRocks Docs 问题1&#xff1a;实时写入starrocks &#xff0c;配置参数设置如下&a…

【【51单片机11.0592晶振红外遥控】】

51单片机11.0592晶振红外遥控 红外遥控&#xff0c;51单片机完结 这是初步实现的架构 怎么实现内部的详细逻辑 我们用状态机的方法 0状态时一个空闲状态 当它接收到下降沿开始计时然后转为1状态 1状态下 寻找start 或者repeat的信号 再来下降沿读出定时器的值 如果是start 那…

华为eNSP:isis的配置

一、拓扑图 二、路由器的配置 配置接口IP AR1&#xff1a; <Huawei>system-view [Huawei]int g0/0/0 [Huawei-GigabitEthernet0/0/0]ip add 1.1.1.1 24 [Huawei-GigabitEthernet0/0/0]qu AR2: <Huawei>system-view [Huawei]int g0/0/0 [Huawei-GigabitEthe…

【沁恒蓝牙mesh】数据收发接口与应用层模型传递

本文主要描述了沁恒蓝牙mesh SDK的蓝牙数据收发接口&#xff0c;以及应用层的回调函数解析以及模型传递 这里写目录标题 1. 数据收发接口1.1【发送数据】1.2 【数据接收】 2. 应用层模型分析 1. 数据收发接口 1.1【发送数据】 /*&#xff08;1&#xff09;接口1 */ /*接口一&…

力扣热门100题之最大子数组和【中等】【动态规划】

题目描述 给你一个整数数组 nums &#xff0c;请你找出一个具有最大和的连续子数组&#xff08;子数组最少包含一个元素&#xff09;&#xff0c;返回其最大和。 子数组 是数组中的一个连续部分。 示例 1&#xff1a; 输入&#xff1a;nums [-2,1,-3,4,-1,2,1,-5,4] 输出&a…

Java文字转语音功能实现

需要下载jacob-1.18&#xff0c;网上一堆 项目中引入pom文件 <!-- https://mvnrepository.com/artifact/com.jacob/jacob 文字转语音 --> <dependency><groupId>com.hynnet</groupId><artifactId>jacob</artifactId><version>1.18&…

Go语言中‘String’包中的‘Cut‘函数的实现

Go语言中‘String’包中的’Cut’函数的实现 ​ Cut函数用于在字符串**‘s’中查找子串’sep’&#xff0c;并将字符串’s’在子串 ‘sep’ 第一次出现的位置分割成两部分&#xff1a;before和after** package main import("fmt" "strings" ) func main(…

Python初学

认识计算机&#xff1a; 计算机是怎么构成的——冯诺依曼体系结构 内存外存的区别&#xff1a; 一般机械硬盘的存储数据的期限是&#xff1a;几年到十几年 什么是编程&#xff1f; 制作软件——编程/软件开发 编程语言有哪些&#xff1f; Python Python背景知识 荷兰人Guid…

M5ATOMS3基础02传感器MPU6886

M5ATOMS3基础01按键 简洁版本 MPU6886是一款6轴IMU单元&#xff0c;具有3轴重力加速度计和3轴陀螺仪。它采用16位ADC&#xff0c;内置可编程数字滤波器和片上温度传感器&#xff0c;并通过I2C接口&#xff08;地址为0x68&#xff09;与上位机通信。MPU6886支持低功耗模式&#…

Linux系统MySQL中用户的权限管理

本节主要学习用户权限管理的概述&#xff0c;用户权限类型&#xff0c;用户赋权&#xff0c;权限删除&#xff0c;用户删除等。 目录 一、概述 二、用户权限类型 三、用户赋权 四、权限删除 五、用户删除 一、概述 数据库用户权限管理是数据库系统中非常重要的一个方面&am…

如何理解 SpringBoot 的生命周期

生命周期&#xff0c;就是从诞生到死亡的一个过程。Spring 中 bean 也是如此。在 Spring 中的 ConfigurableApplicationContext 的 refresh 开始直到一个 bean 可以被正常使用&#xff0c;中间经历了一系列初始化操作&#xff1b;而在其 close 后又经历了一系列销毁操作&#x…

(八九)如何与InfluxDB交互InfluxDB HTTP API

以下内容来自 尚硅谷&#xff0c;写这一系列的文章&#xff0c;主要是为了方便后续自己的查看&#xff0c;不用带着个PDF找来找去的&#xff0c;太麻烦&#xff01; 第 8 章 前言&#xff1a;如何与InfluxDB交互 1、InfluxDB启动后&#xff0c;会向外提供一套HTTP API。外部程…