当前位置: 首页 > news >正文

SpringMVC 6+源码分析(一)初始化流程

一、概述

本文将深入剖析SpringBoot启动原理,揭示ServletContainerInitializer如何配合@HandlesTypes及SpringBootServletInitializer,实现应用启动。从SPI机制到容器初始化,全面解读。

二、初始化流程

2.1初始化容器

当SpringBoot项目启动以后,启动类型为web Application类型并引入相关web模块后。会启动一个Servlet容器,容器启动以后将去扫描每个jar包的ServletContainerInitializer的实现。
根据servlet开发规范,ServletContainerInitializer接口的实现类,必须在META-INF/services/javax.servlet.ServletContainerInitializer文件里面进行声明,声明的内容就是实现ServletContainerInitializer接口的全类名。(该声明使用的)
请添加图片描述
如上图,Spring 6.0x 的声明是在spring-web模块下。其ServletContainerInitializer的实现类为org.springframework.web.SpringServletContainerInitializer。

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {@Overridepublic void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)throws ServletException {List<WebApplicationInitializer> initializers = Collections.emptyList();if (webAppInitializerClasses != null) {initializers = new ArrayList<>(webAppInitializerClasses.size());for (Class<?> waiClass : webAppInitializerClasses) {// Be defensive: Some servlet containers provide us with invalid classes,// no matter what @HandlesTypes says...if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&WebApplicationInitializer.class.isAssignableFrom(waiClass)) {try {initializers.add((WebApplicationInitializer)ReflectionUtils.accessibleConstructor(waiClass).newInstance());}catch (Throwable ex) {throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);}}}}if (initializers.isEmpty()) {servletContext.log("No Spring WebApplicationInitializer types detected on classpath");return;}servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");AnnotationAwareOrderComparator.sort(initializers);for (WebApplicationInitializer initializer : initializers) {initializer.onStartup(servletContext);}}}

可以看到 SpringServletContainerInitializer 重写了,onStartup()方法。其中第一个参数传入的是 类上@HandlesTypes 注解的值即 WebApplicationInitializer.class。
拿到WebApplicationInitializer 之后 获取到其实现类并始终反射原理对实现类进行初始化。
根据源码,我们找到了其实现类 有四个。我们重点查看AbstractDispatcherServletInitializer 它是用来初始化 DispatcherServlet的。请添加图片描述
看到onStartup()-> registerDispatcherServlet()方法,

protected void registerDispatcherServlet(ServletContext servletContext) {String servletName = getServletName();Assert.state(StringUtils.hasLength(servletName), "getServletName() must not return null or empty");WebApplicationContext servletAppContext = createServletApplicationContext();Assert.state(servletAppContext != null, "createServletApplicationContext() must not return null");FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);Assert.state(dispatcherServlet != null, "createDispatcherServlet(WebApplicationContext) must not return null");dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);if (registration == null) {throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +"Check if there is another servlet registered under the same name.");}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);}

该方法创建了dispatcherServlet,并将其注册到容器中。

2.2 dispatcherServlet的初始化

由于 DispatcherServlet继承 FrameworkServlet,FrameworkServlet继承HttpServletBean,而 HttpServletBean 实际是一个 HttpServlet 对象。所以在DispatcherServlet对象被创建时,实际上是创建了一个 HttpServlet 对象。
我们在 AbstractDispatcherServletInitializer类的createDispatcherServlet()中看到其创建了一个 DispatcherServlet对象。

	protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {return new DispatcherServlet(servletAppContext);}

当HttpServlet对象被容器创建是,就会执行 init()方法,如源码中 HttpServletBean 的init()方法。

@Overridepublic 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();}

该方法初始化配置了servlet参数,而其中initServletBean() 方法则由其子类FrameworkServlet实现。

@Overrideprotected 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");}}

该方法中的 this.webApplicationContext = 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 cwac && !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;}

根据业务逻辑代码将进入 configureAndRefreshWebApplicationContext(cwac)方法。

	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 cwe) {cwe.initPropertySources(getServletContext(), getServletConfig());}postProcessWebApplicationContext(wac);applyInitializers(wac);wac.refresh();}

该方法除了设置setServletConfig之外,还注册了一个 事件监听器,wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

/*** ApplicationListener endpoint that receives events from this servlet's WebApplicationContext* only, delegating to {@code onApplicationEvent} on the FrameworkServlet instance.*/private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {@Overridepublic void onApplicationEvent(ContextRefreshedEvent event) {FrameworkServlet.this.onApplicationEvent(event);}}

该监听器用于监听spring上下文刷新时间,等SpringIoc容器初始化完成,发送上下文刷新事件后。对应的Listener方法接收到以后,就会执行onRefresh()方法。

	public void onApplicationEvent(ContextRefreshedEvent event) {this.refreshEventReceived = true;synchronized (this.onRefreshMonitor) {onRefresh(event.getApplicationContext());}}

onRefresh(ApplicationContext context)的具体实现在DispatcherServlet 类中,

	/*** This implementation calls {@link #initStrategies}.*/@Overrideprotected void onRefresh(ApplicationContext context) {initStrategies(context);}/*** Initialize the strategy objects that this servlet uses.* <p>May be overridden in subclasses in order to initialize further strategy objects.*/protected void initStrategies(ApplicationContext context) {initMultipartResolver(context);initLocaleResolver(context);initThemeResolver(context);initHandlerMappings(context);initHandlerAdapters(context);initHandlerExceptionResolvers(context);initRequestToViewNameTranslator(context);initViewResolvers(context);initFlashMapManager(context);}

这时将开始对DispatcherServlet的初始化,
initMultipartResolver(context); // 初始化文件上传解析器
initLocaleResolver(context); // 初始化国际化解析器
initThemeResolver(context); // 初始化主题解析器
initHandlerMappings(context); // 初始化处理器映射器
initHandlerAdapters(context); // 初始化处理器适配器
initHandlerExceptionResolvers(context); // 初始化异常解析器
initRequestToViewNameTranslator(context); // 初始化视图名称翻译器
initViewResolvers(context); // 初始化视图解析器
initFlashMapManager(context); // 初始化FlashMap管理器

initStrategies()里面的初始化流程比较长,将再下一章节再详细讲解。

三、结尾

本文只是对SpringMVC在SpringBoot的注解模式下如何整合初始化的流程作讲解,如有不对的地方,欢迎指正相互学习。

http://www.lryc.cn/news/605248.html

相关文章:

  • 2021 年 NOI 最后一题题解
  • 项目文档太多、太混乱怎么解决
  • C语言高级(构造数据类型)
  • 2020 年 NOI 最后一题题解
  • REST、GraphQL、gRPC、tRPC深度对比
  • 订阅区块,部署合约,加载合约
  • 颐顿机电携手观远BI数据:以数据驱动决策,领跑先进制造智能化升级
  • 流程制造的数字孪生:从黑箱生产到全息掌控
  • Linux c网络专栏第四章io_uring
  • Linux零基础Shell教学全集(可用于日常查询语句,目录清晰,内容详细)(自学尚硅谷B站shell课程后的万字学习笔记,附课程链接)
  • Baumer工业相机堡盟工业相机如何通过YoloV8的深度学习模型实现汽车牌照的位置识别(C#代码,UI界面版)
  • 大厂主力双塔模型实践与线上服务
  • SSRF漏洞基础
  • 爬虫验证码处理:ddddocr 的详细使用(通用验证码识别OCR pypi版)
  • Redis 中 key 的过期策略 和 定时器的两种实现方式
  • cocos打包web端需要注意的地方
  • Apache HTTP Server 2.4.50 路径穿越漏洞(CVE-2021-42013)
  • Baumer工业相机堡盟工业相机如何通过YoloV8深度学习模型实现裂缝的检测识别(C#代码UI界面版)
  • 生成式推荐网络架构汇总
  • Java注解与反射:从自定义注解到框架设计原理
  • CHI - Transaction介绍(4) - 原子操作
  • 工厂方法模式:从基础到C++实现
  • Spring Boot 数据源配置中为什么可以不用写 driver-class-name
  • 1. ESP开发之实体按键(KEYPADBUTTON)控制LVGL控件
  • 一文掌握最新版本Monocle3单细胞轨迹(拟时序)分析
  • 【Unity】在构建好的项目里创建自定义文件夹
  • Thales靶机
  • Redis知识点(1)
  • 【力扣热题100】哈希——字母异位词分组
  • 【c++】leetcode763 划分字母区间