SpringBoot的底层入口SpringServletContainerInitializer 野性酷女 2022-11-08 11:23 42阅读 0赞 #### SpringServletContainerInitializer #### 参考之前的博文[无配置文件SSM][SSM] 那么容器启动时候,为什么这么`MyWebAppInitializer`这个类会被加载呢,进而创建根容器,创建web的ioc容器呢 **来看这个类的介绍:** > WebApplicationInitializer是Spring MVC提供的一个接口,它确保检测到您的实现并自动用于初始化Servlet 3容器。WebApplicationInitializer的抽象基类实现AbstractDispatcherServletInitializer通过重写方法来指定servlet映射和DispatcherServlet配置的位置,使得注册DispatcherServlet更加容易。 这里提到了初始化servlet3.0容器,那就有必要了解一下sevrvlet3.0的一个初始化规范,根据官方文档的解释,用自己的话总结一下有关的重要几点,大概意思就是: > web容器在启动的时候,会扫描每个jar包下的META-INF/services/javax.servlet.ServletContainerInitializer,加载这个文件制定的类,并且可以通过@HandlesTypes注解,把感兴趣的类的信息,注入到`onStartup(Set<Class<?>> var1, ServletContext var2)`var1当中。 **经过查看源码,以及debug调试发现,这个ServletContainerInitializer**其实就是`SpringServletContainerInitializer`,来看SpringServletContainerInitializer所在在包下的目录结构。 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzczMjk1NQ_size_16_color_FFFFFF_t_70] **根据上面介绍的servlet3.0规范,当servlet容器启动的时候,就会加载javax.servlet.ServletContainerInitializer文件中指定的类** 其内容就是`org.springframework.web.SpringServletContainerInitializer`,所有也就会在容器启动的时候,加载`SpringServletContainerInitializer` **SpringServletContainerInitializer代码如下:** /* * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.web; import java.lang.reflect.Modifier; import java.util.LinkedList; import java.util.List; import java.util.ServiceLoader; import java.util.Set; import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.annotation.HandlesTypes; import org.springframework.core.annotation.AnnotationAwareOrderComparator; @HandlesTypes(WebApplicationInitializer.class) public class SpringServletContainerInitializer implements ServletContainerInitializer { @Override public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException { List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>(); if (webAppInitializerClasses != null) { 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) 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); //遍历容器中所有的WebApplicationInitializer#onStartup方法 for (WebApplicationInitializer initializer : initializers) { initializer.onStartup(servletContext); } } } 这段代码的简单的介绍一下:因为这个类上标注了`@HandlesTypes(WebApplicationInitializer.class)`,所以会加载所有`WebApplicationInitializer`信息都会被注入到`onStartup()`方法的形参`webAppInitializerClasses`上,然后遍历,判断如果不是接口【!waiClass.isInterface()】,不是抽象类【!Modifier.isAbstract(waiClass.getModifiers()】,那么就实例化,并且添加在集合当中。最后遍历集合`initializers`,代用每一个对象的`#onStartup(servletContext)`方法。在这个遍历上,打上一个断点,看一下`initializers`中有哪些对象。 for (WebApplicationInitializer initializer : initializers) { initializer.onStartup(servletContext); } 查看结果如下:**(加载的感兴趣的类信息一共有4个,因为其他三个都是抽象类,不符合实例化的条件,所有集合中也就一个类,就是我们定义的`MyWebAppInitializer`)** ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzczMjk1NQ_size_16_color_FFFFFF_t_70 1] **经过上面的分析,可以大概小结一下: 因为servlet3.0容器加载规范,会加载特定位置的文件中指定的类,在这里也就是SpringServletContainerInitializer,,然后加载@HandleType注解标注的感兴趣的类,然后根据条件实例化这些类,添加到集合中,遍历集合然后调用他们的onStartup方法** -------------------- 有了这些基础,那么就可以来看一下,他的执行调用过程,来看一下容器如何被创建的,核心控制器DispatcherServlet是如何被添加到容器中… 执行`initializer.onStartup(servletContext)`,所以来到`MyWebAppInitializer#onStartup`的方法,因为他本身没有重写这个方法,所以往上找他的父类`AbstractAnnotationConfigDispatcherServletInitializer`,但是这里也没有那么就接着再找父类`AbstractDispatcherServletInitializer`,在这个类中`onStartup`方法代码如下: @Override public void onStartup(ServletContext servletContext) throws ServletException { super.onStartup(servletContext); registerDispatcherServlet(servletContext); } **他分为两步,先执行`super.onStartup(servletContext)`**,所以接着来到他的父类`AbstractContextLoaderInitializer`中这个方法的实现, @Override public void onStartup(ServletContext servletContext) throws ServletException { registerContextLoaderListener(servletContext); } protected void registerContextLoaderListener(ServletContext servletContext) { //调用#createRootApplicationContext()方法,因为自己没有实现,调用子类的方法 //执行AbstractAnnotationConfigDispatcherServletInitializer这个类的createRootApplicationContext() WebApplicationContext rootAppContext = createRootApplicationContext(); if (rootAppContext != null) { //添加容器监听事件 ContextLoaderListener listener = new ContextLoaderListener(rootAppContext); listener.setContextInitializers(getRootApplicationContextInitializers()); servletContext.addListener(listener); } else { logger.debug("No ContextLoaderListener registered, as " + "createRootApplicationContext() did not return an application context"); } } 进入`AbstractAnnotationConfigDispatcherServletInitializer` @Override protected WebApplicationContext createRootApplicationContext() { //getRootConfigClasses()方法,这个类自己没有实现,调用的其实是MyWebAppInitializer#getRootConfigClasses方法 Class<?>[] configClasses = getRootConfigClasses(); if (!ObjectUtils.isEmpty(configClasses)) { //创建一个根容器 AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext(); //把我们的配置类注册进去 rootAppContext.register(configClasses); return rootAppContext; } else { return null; } } **执行到这里,super.onStartup(servletContext),方法执行完,根容器已经被创建出来了,接着调用registerDispatcherServlet(servletContext)这个方法,方法如下:** protected void registerDispatcherServlet(ServletContext servletContext) { //获得名字 默认是dispatcher String servletName = getServletName(); Assert.hasLength(servletName, "getServletName() must not return empty or null"); // 因为本类没有实现,调动子类AbstractAnnotationConfigDispatcherServletInitializer的 //createServletApplicationContext方法,创建出web的ioc容器 这个方法的代码与createRootApplicationContext()的执行过程类似 /*调用子类MyWebAppInitializer 的getServletConfigClasses方法,加载AppConfig.class 然后创建出一个容器。 */ WebApplicationContext servletAppContext = createServletApplicationContext(); Assert.notNull(servletAppContext, "createServletApplicationContext() did not return an application " + "context for servlet [" + servletName + "]"); //其实就是new DispatcherServlet(servletAppContext),创建出一个DispatcherServlet FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext); dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers()); // 将创建出来的DispatcherServlet添加到容器中 ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet); Assert.notNull(registration, "Failed to register servlet with name '" + servletName + "'." + "Check if there is another servlet registered under the same name."); //容器加载,就创建 registration.setLoadOnStartup(1); //设置拦截的mapping 回调子类的getServletMappings()方法 也就是MyWebAppInitializer#getServletMappings方法 registration.addMapping(getServletMappings()); //设置异步支持 默认是true registration.setAsyncSupported(isAsyncSupported()); //添加过滤器,可以通过重写ServletFilters方法 ,来添加过滤器 Filter[] filters = get ServletFilters(); if (!ObjectUtils.isEmpty(filters)) { for (Filter filter : filters) { registerServletFilter(servletContext, filter); } } customizeRegistration(registration); } **执行到这里,作用是创建出web的ioc容器,并且创建出DispatcherServlet,设置了他的启动机制,设置了ServletMapping,所有就不需要的web.xml配置,就能成功启动容器。** (其实根容器和web容器其实是一个父子关系),每一个容器,装载一些特定的组件。 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzczMjk1NQ_size_16_color_FFFFFF_t_70 2] > DispatcherServlet需要一个WebApplicationContext(一个普通ApplicationContext的扩展)来进行自己的配置。WebApplicationContext有一个指向它关联的ServletContext和Servlet的链接。它还绑定到ServletContext,以便应用程序可以在requestcontext tutils上使用静态方法来查找需要访问的WebApplicationContext。 > 对于许多只有一个WebApplicationContext的应用程序来说,这是简单而充分的。还可以有一个上下文层次结构,其中一个根WebApplicationContext在多个DispatcherServlet(或其他Servlet)实例之间共享,每个实例都有自己的子WebApplicationContext配置。有关上下文层次结构特性的更多信息, > 根WebApplicationContext通常包含基础设施bean,比如需要跨多个Servlet实例共享的数据存储库和业务服务。这些bean是有效继承的,可以在特定于Servlet的子WebApplicationContext中重写(即重新声明),该上下文通常包含给定Servlet的本地 **总结一下,画了一个uml时序图:** ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzczMjk1NQ_size_16_color_FFFFFF_t_70 3] * **web容器在启动的时候,会扫描每个jar包下的META-INF/services/javax.servlet.ServletContainerInitializer** * **spring的应用一启动会加载感兴趣的WebApplicationInitializer接口的下的所有组件;** * **并且为WebApplicationInitializer组件创建对象(组件不是接口,不是抽象类)** [SSM]: https://blog.csdn.net/weixin_43732955/article/details/92002510 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzczMjk1NQ_size_16_color_FFFFFF_t_70]: /images/20221023/9a23eec5b8b64f38993134003f44d914.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzczMjk1NQ_size_16_color_FFFFFF_t_70 1]: /images/20221023/20adfc14d3724d75895fd4efe19aa070.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzczMjk1NQ_size_16_color_FFFFFF_t_70 2]: /images/20221023/1d8bda8677e84746b14276154771ef0f.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzczMjk1NQ_size_16_color_FFFFFF_t_70 3]: /images/20221023/b1de06b1f703423489cce0335cd49f69.png
相关 springboot_启动类的入口 SpringBoot打jar包启动类的入口![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9i... 一时失言乱红尘/ 2024年04月18日 06:52/ 0 赞/ 69 阅读
相关 基础入门 - SpringBoot 底层注解 目录 1、SpringBoot特点 1.1、依赖管理 1.2、自动配置 2、容器功能 2.1、组件添加 1、@Configuration Spring Boot ゝ一世哀愁。/ 2024年03月31日 16:07/ 0 赞/ 110 阅读
相关 SpringBoot-底层注解-@configuration 底层注解:@configuration 先前说明 用在Config类中,用于添加组件,类似于传统的配置xml文件,Config下还有一个@Bean注解用来注册组件 傷城~/ 2023年10月04日 13:27/ 0 赞/ 78 阅读
相关 SpringBoot底层注解详解 一、 @Configuration 我们先来看看,以前我们用spring是怎么配置bean的属性值: ![format_png][] 虽然不算多,但是如果每一个bea 冷不防/ 2023年09月27日 22:12/ 0 赞/ 177 阅读
相关 SpringBoot:SpringBoot 的底层运行原理解析 声明原文出处:狂神说 文章目录 1. pom.xml 1 . 父依赖 旧城等待,/ 2023年09月24日 23:35/ 0 赞/ 86 阅读
相关 springboot原理实战(4)-springboot入口分析 文章目录 目录 一、环境搭建2种方式 ①继承父组件 ②第2种引入方式: 二.@SpringBootApplication注解分 喜欢ヅ旅行/ 2023年07月13日 03:58/ 0 赞/ 20 阅读
相关 SpringBoot的底层入口SpringServletContainerInitializer SpringServletContainerInitializer 参考之前的博文[无配置文件SSM][SSM] 那么容器启动时候,为什么这么`MyWebAppIni 野性酷女/ 2022年11月08日 11:23/ 0 赞/ 43 阅读
相关 SpringBoot2源码分析1-程序入口 SpringBoot2源码分析 1-程序入口 SpringBoot的启动方式 1、在IDE中使用 main方法启动 ![在这里插入图片描述][wa ╰半橙微兮°/ 2022年09月04日 10:53/ 0 赞/ 192 阅读
相关 Springboot 入口类及其实现自动配置的原理 入口类需要放在包的最外层在,能够扫描到所有子包中的类 @SpringBootApplication注解将Application类注册为启动类 package com 雨点打透心脏的1/2处/ 2022年06月12日 01:53/ 0 赞/ 169 阅读
还没有评论,来说两句吧...