1.SpringBoot对SpringMVC的支持

对于一个 SpringBoot web 工程来说,一个主要的依赖标志就是有 spring-boot-starter-web 这个 starter ,spring-boot-starter-web 模块在 spring boot 中其实并没有代码存在,只是在 pom.xml 中携带了一些依赖,包括 web、webmvc、tomcat 等:

<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><modelVersion>4.0.0</modelVersion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.4.5</version><name>spring-boot-starter-web</name><description>Starter for building web, including RESTful, applications using Spring MVC. Uses Tomcat as the default embedded container</description><url>https://spring.io/projects/spring-boot</url><organization><name>Pivotal Software, Inc.</name><url>https://spring.io</url></organization><licenses><license><name>Apache License, Version 2.0</name><url>https://www.apache.org/licenses/LICENSE-2.0</url></license></licenses><developers><developer><name>Pivotal</name><email>info@pivotal.io</email><organization>Pivotal Software, Inc.</organization><organizationUrl>https://www.spring.io</organizationUrl></developer></developers><scm><connection>scm:git:git://github.com/spring-projects/spring-boot.git</connection><developerConnection>scm:git:ssh://git@github.com/spring-projects/spring-boot.git</developerConnection><url>https://github.com/spring-projects/spring-boot</url></scm><issueManagement><system>GitHub</system><url>https://github.com/spring-projects/spring-boot/issues</url></issueManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><version>2.4.5</version><scope>compile</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-json</artifactId><version>2.4.5</version><scope>compile</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId><version>2.4.5</version><scope>compile</scope></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>5.3.6</version><scope>compile</scope></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.3.6</version><scope>compile</scope></dependency></dependencies>
</project>

web、webmvc、tomcat 等提供了 web 应用的运行环境,那 spring-boot-starter 则是让这些运行环境工作的开关(因为 spring-boot-starter 中会间接引入 spring-boot-autoconfigure )。

WebServer 自动配置

在 spring-boot-autoconfigure 模块中,有处理关于 WebServer 的自动配置类 ServletWebServerFactoryAutoConfiguration 。

ServletWebServerFactoryAutoConfiguration

代码片段如下:

@Configuration(proxyBeanMethods = false
)
@AutoConfigureOrder(Integer.MIN_VALUE)
@ConditionalOnClass({ServletRequest.class})
@ConditionalOnWebApplication(type = Type.SERVLET
)
@EnableConfigurationProperties({ServerProperties.class})
@Import({BeanPostProcessorsRegistrar.class, ServletWebServerFactoryConfiguration.EmbeddedTomcat.class, ServletWebServerFactoryConfiguration.EmbeddedJetty.class, ServletWebServerFactoryConfiguration.EmbeddedUndertow.class})
public class ServletWebServerFactoryAutoConfiguration {@Beanpublic ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties, ObjectProvider<WebListenerRegistrar> webListenerRegistrars) {return new ServletWebServerFactoryCustomizer(serverProperties, (List)webListenerRegistrars.orderedStream().collect(Collectors.toList()));}@Bean@ConditionalOnClass(name = {"org.apache.catalina.startup.Tomcat"})public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(ServerProperties serverProperties) {return new TomcatServletWebServerFactoryCustomizer(serverProperties);}
}

两个 Condition 表示当前运行环境是基于 servlet 标准规范的 web 服务:

  • ConditionalOnClass(ServletRequest.class) : 表示当前必须有 servlet-api 依赖存在
  • ConditionalOnWebApplication(type = Type.SERVLET) :仅基于servlet的Web应用程序

@EnableConfigurationProperties(ServerProperties.class):ServerProperties 配置中包括了常见的 server.port 等配置属性。

通过 @Import 导入嵌入式容器相关的自动配置类,有 EmbeddedTomcat、EmbeddedJetty 和EmbeddedUndertow。

综合来看,ServletWebServerFactoryAutoConfiguration 自动配置类中主要做了以下几件事情:

  • 导入了内部类 BeanPostProcessorsRegistrar,它实现了 ImportBeanDefinitionRegistrar,可以实现ImportBeanDefinitionRegistrar 来注册额外的 BeanDefinition。
  • 导入了 ServletWebServerFactoryConfiguration.EmbeddedTomcat 等嵌入容器先关配置(我们主要关注tomcat 相关的配置)。
  • 注册了ServletWebServerFactoryCustomizer、TomcatServletWebServerFactoryCustomizer 两个WebServerFactoryCustomizer 类型的 bean。

下面就针对这几个点,做下详细的分析。

BeanPostProcessorsRegistrar

public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {private ConfigurableListableBeanFactory beanFactory;public BeanPostProcessorsRegistrar() {}public void setBeanFactory(BeanFactory beanFactory) throws BeansException {if (beanFactory instanceof ConfigurableListableBeanFactory) {this.beanFactory = (ConfigurableListableBeanFactory)beanFactory;}}public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {if (this.beanFactory != null) {// 注册 WebServerFactoryCustomizerBeanPostProcessorthis.registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor", WebServerFactoryCustomizerBeanPostProcessor.class, WebServerFactoryCustomizerBeanPostProcessor::new);// 注册 errorPageRegistrarBeanPostProcessorthis.registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor", ErrorPageRegistrarBeanPostProcessor.class, ErrorPageRegistrarBeanPostProcessor::new);}}private <T> void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry, String name, Class<T> beanClass, Supplier<T> instanceSupplier) {if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(beanClass, true, false))) {RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass, instanceSupplier);beanDefinition.setSynthetic(true);registry.registerBeanDefinition(name, beanDefinition);}}
}

上面这段代码中,注册了两个 bean,一个 WebServerFactoryCustomizerBeanPostProcessor,一个 errorPageRegistrarBeanPostProcessor;这两个都实现类 BeanPostProcessor 接口,属于 bean 的后置处理器,作用是在 bean 初始化前后加一些自己的逻辑处理。

  • WebServerFactoryCustomizerBeanPostProcessor:作用是在 WebServerFactory 初始化时调用上面自动配置类注入的那些 WebServerFactoryCustomizer ,然后调用 WebServerFactoryCustomizer 中的 customize 方法来 处理 WebServerFactory。
  • errorPageRegistrarBeanPostProcessor:和上面的作用差不多,不过这个是处理 ErrorPageRegistrar 的。

下面简单看下 WebServerFactoryCustomizerBeanPostProcessor 中的代码:

//在WebServerFactory初始化前public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {if (bean instanceof WebServerFactory) {this.postProcessBeforeInitialization((WebServerFactory)bean);}return bean;
}public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {return bean;
}private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {((LambdaSafe.Callbacks)LambdaSafe.callbacks(WebServerFactoryCustomizer.class, this.getCustomizers(), webServerFactory, new Object[0]).withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)).invoke((customizer) -> {// 这段代码就是拿到所有的 Customizers ,然后遍历调用这些 Customizers 的 customize 方法customizer.customize(webServerFactory);});
}

两个 Customizer Bean

这两个 Customizer 实际上就是去处理一些配置值,然后绑定到 各自的工厂类的。

WebServerFactoryCustomizer

将 serverProperties 配置值绑定给 ConfigurableServletWebServerFactory 对象实例上。

public void customize(ConfigurableServletWebServerFactory factory) {PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();ServerProperties var10001 = this.serverProperties;var10001.getClass();map.from(var10001::getPort).to(factory::setPort);var10001 = this.serverProperties;var10001.getClass();map.from(var10001::getAddress).to(factory::setAddress);ServerProperties.Servlet var5 = this.serverProperties.getServlet();var5.getClass();map.from(var5::getContextPath).to(factory::setContextPath);var5 = this.serverProperties.getServlet();var5.getClass();map.from(var5::getApplicationDisplayName).to(factory::setDisplayName);var5 = this.serverProperties.getServlet();var5.getClass();map.from(var5::isRegisterDefaultServlet).to(factory::setRegisterDefaultServlet);var5 = this.serverProperties.getServlet();var5.getClass();map.from(var5::getSession).to(factory::setSession);var10001 = this.serverProperties;var10001.getClass();map.from(var10001::getSsl).to(factory::setSsl);var5 = this.serverProperties.getServlet();var5.getClass();map.from(var5::getJsp).to(factory::setJsp);var10001 = this.serverProperties;var10001.getClass();map.from(var10001::getCompression).to(factory::setCompression);var10001 = this.serverProperties;var10001.getClass();map.from(var10001::getHttp2).to(factory::setHttp2);var10001 = this.serverProperties;var10001.getClass();map.from(var10001::getServerHeader).to(factory::setServerHeader);var5 = this.serverProperties.getServlet();var5.getClass();map.from(var5::getContextParameters).to(factory::setInitParameters);map.from(this.serverProperties.getShutdown()).to(factory::setShutdown);Iterator var3 = this.webListenerRegistrars.iterator();while(var3.hasNext()) {WebListenerRegistrar registrar = (WebListenerRegistrar)var3.next();registrar.register(factory);}}
TomcatServletWebServerFactoryCustomizer

相比于上面那个,这个 customizer 主要处理 Tomcat 相关的配置值

public void customize(TomcatServletWebServerFactory factory) {// 拿到 tomcat 相关的配置ServerProperties.Tomcat tomcatProperties = this.serverProperties.getTomcat();// server.tomcat.additional-tld-skip-patternsif (!ObjectUtils.isEmpty(tomcatProperties.getAdditionalTldSkipPatterns())) {factory.getTldSkipPatterns().addAll(tomcatProperties.getAdditionalTldSkipPatterns());}// server.redirectContextRootif (tomcatProperties.getRedirectContextRoot() != null) {this.customizeRedirectContextRoot(factory, tomcatProperties.getRedirectContextRoot());}// server.useRelativeRedirectsthis.customizeUseRelativeRedirects(factory, tomcatProperties.isUseRelativeRedirects());factory.setDisableMBeanRegistry(!tomcatProperties.getMbeanregistry().isEnabled());
}private void customizeRedirectContextRoot(ConfigurableTomcatWebServerFactory factory, boolean redirectContextRoot) {factory.addContextCustomizers(new TomcatContextCustomizer[]{(context) -> {context.setMapperContextRootRedirectEnabled(redirectContextRoot);}});
}private void customizeUseRelativeRedirects(ConfigurableTomcatWebServerFactory factory, boolean useRelativeRedirects) {factory.addContextCustomizers(new TomcatContextCustomizer[]{(context) -> {context.setUseRelativeRedirects(useRelativeRedirects);}});
}

2. Springboot何时创建的TomcatServer?

SpringBoot在context刷新过程中的onrefresh方法完成Tomcat的内嵌.

createWebServer()的方法这块有点绕,总结一下整体执行顺序是

  1. 先创建WebServerFactory, 注入TomcatServletWebServerFactory到SpringIOC容器中
  2. 调用getSelfInitializer()方法,该方法内部实现是函数式接口,所以会返回一个匿名内部类对象servletContextInitializer,这个函数式接口包含的唯一方法是selfInitialize()
  3. 调用2.1.2的方法getWebServer(),传入的参数是步骤2的servletContextInitializer
    1. 创建Tomcat对象
    2. 准备容器上下文prepareContext
      1. 创建好webApplicationContext(实际类型是TomcatEmbeddedContext)
      2. configureContext(context, initializersToUse):将servletContextInitializer包装为容器初始化器并添加到webApplicationContext中。
    3. getTomcatWebServer
      1. new TomcatWebServer(tomcat, getPort() >= 0); 创建一个tomcat的web服务

        1. TomcatWebServer的构造器会调用initialize()方法,

          1. 查找context(TomcatEmbeddedContext),添加生命周期监听,启动守护线程,执行tomcat.start()

            1. tomcat.start()会回调容器初始化器的onstartup方法,回调步骤2的selfInitialize(),此时会传入参数ServletContext,若没有则会创建一个。
  4. selfInitialize() tomcat启动后扫描容器初始化器的回调
    1. 当前SpringBoot的AnnotationConfigServletWebServerApplicationContextwebApplicationContext作为rootApplicationContext放入到了ServletContext中
    2. 注册Servlet作用域application到BeanFactory中
    3. 注册servlet环境的bean到BeanFactory中
      (servletContext,servletConfig,contextAttributes)三个Bean
    4. getServletContextInitializerBeans()
      1. 扫描BeanFactory中的ServletContextInitializer.class类型的Bean放入到initializers 包括**DispatcherServletRegistrationBean(由自动配置导入,内部包含DispatchServlet的Bean)**和FilterRegistrationBean,
    5. 执行ServletContextInitializerBeans的onStartup方法
      1. 扫描到DispatcherServletRegistrationBean,执行Onstartup方法

        1. 执行register方法,将内部的DispatchServlet加入到ServletContext中。

注:DispatcherServlet在执行init方法时,会初始化上下文环境即mvcContext

SpringMVC中mvcContext和应用Context不是同一个

在SpringBoot中mvcContext和应用Context相同

2.1 onrefresh()/createWebServer()

/*org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#onRefresh */
protected void onRefresh() {//父类方法,设置主题资源super.onRefresh();try {  //tomcat启动createWebServer();}catch (Throwable ex) {throw new ApplicationContextException("Unable to start web server", ex);}
}private void createWebServer() {WebServer webServer = this.webServer;ServletContext servletContext = getServletContext();if (webServer == null && servletContext == null) {//自动配置类ServletWebServerFactoryAutoConfiguration//@Import ServletWebServerFactoryConfiguration.EmbeddedTomcat.class//初始化TomcatServletWebServerFactory到SpringIOC容器中//注意初始化多个ServletWebServerFactory会报错ServletWebServerFactory factory = getWebServerFactory();//工厂方法创建webserver//Tomcat tomcat = new tomcat();//tomcat的属性修改//tomcat.start()//另启动一个端口接受tomcat停止命令this.webServer = factory.getWebServer(getSelfInitializer());}else if (servletContext != null) {try {//servletContext != null时 获得初始化器,自行调用onstartup方法getSelfInitializer().onStartup(servletContext);}catch (ServletException ex) {throw new ApplicationContextException("Cannot initialize servlet context", ex);}}//根据environment初始化servletContext的属性initPropertySources();
}

SpringBoot在容器刷新过程中会执行CreateWebServer()方法来创建Web服务器,该方法主要有三个步骤:

1.创建WebServerFactory,服务器工厂

2.getSelfInitializer()获得初始化器

3.从服务器工厂创建服务器,传入的参数是步骤2获得的初始化器

主要分析2,3步骤

2.1.1 getSelfInitializer()

private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {return this::selfInitialize;}
// 此处this::selfInitialize返回的是函数式方法实现,类似匿名内部类,而不是直接调用(注:理解了很久,PS:断点一直不进去,很奇怪),此处会返回一个ServletContextInitialzer的实现。所以在创建tomcatServer之前没有调用selfInitialize,而是返回一个匿名内部类的对象,然后进入getWebServer方法。//而后续在prepareContext()方法里,会把ServletContextInitialzer包装为容器初始化器,tomcat.start启动时会把调用ServletContextInitialzer的onstartup方法,而它的onstartup方法就是selfInitialize方法,//这里的ServletContext来自哪里?来自tomcat在启动时处理容器初始化器时会调getServletContext()创建一个。
private void selfInitialize(ServletContext servletContext) throws ServletException {//rootApplicationContext.setServletContext(servletContext)//servletContext.setAttribute(rootApplicationContext)//这里将SpringwebApplicationContext作为rootApplicationContext放入到了ServletContext中//后面DispatcherServlet.init()时,会初始化一个applicationContext//然后applicationContext.setParent(ServletContext.getAttribute(rootApplicationContext))prepareWebApplicationContext(servletContext);//注册Servlet作用域application到BeanFactory中,前面研究springboot启动时注入过session+requestregisterApplicationScope(servletContext);//注册servlet环境的bean到BeanFactory中//servletContext,servletConfig,contextAttributes,bf注册三个BeanWebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);//将DispatchServlet加入servletContext,附录有分析该过程for (ServletContextInitializer beans : getServletContextInitializerBeans()) { //在getServletContextInitializerBeans()方法里beans.onStartup(servletContext);}
}

生成的匿名内部类对象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AaKQBHkk-1668858691868)(C:\Users\86189\AppData\Roaming\Typora\typora-user-images\1668781148478.png)]

//本身是一个函数式接口
@FunctionalInterface
public interface ServletContextInitializer {void onStartup(ServletContext servletContext) throws ServletException;
}

selfInitialize/ 设置rootContext

protected void prepareWebApplicationContext(ServletContext servletContext) {Object rootContext = servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);if (rootContext != null) {if (rootContext == this) {throw new IllegalStateException("Cannot initialize context because there is already a root application context present - check whether you have multiple ServletContextInitializers!");}} else {servletContext.log("Initializing Spring embedded WebApplicationContext");try {servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this);this.setServletContext(servletContext);} }
}

selfInitialize/ getServletContextInitializerBeans()

获取BeanFactory中的DispatcherServlet

/* org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#getServletContextInitializerBeans */
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {return new ServletContextInitializerBeans(getBeanFactory());
}public ServletContextInitializerBeans(ListableBeanFactory beanFactory,Class<? extends ServletContextInitializer>... initializerTypes) {this.initializers = new LinkedMultiValueMap<>();this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes): Collections.singletonList(ServletContextInitializer.class);//initializerTypes = [ServletContextInitializer.class]//扫描BeanFactory中的ServletContextInitializer.class类型的Bean放入到initializers中//DispatcherServletRegistrationBean//FilterRegistrationBeanaddServletContextInitializerBeans(beanFactory);addAdaptableBeans(beanFactory);List<ServletContextInitializer> sortedInitializers = this.initializers.values().stream().flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE)).collect(Collectors.toList());this.sortedList = Collections.unmodifiableList(sortedInitializers);logMappings(this.initializers);
}

addServletContextInitializerBeans(beanFactory):

/* org.springframework.boot.web.servlet.ServletContextInitializerBeans#addServletContextInitializerBeans */
private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {for (Class<? extends ServletContextInitializer> initializerType : this.initializerTypes) {for (Entry<String, ? extends ServletContextInitializer> initializerBean : getOrderedBeansOfType(beanFactory, initializerType)) {addServletContextInitializerBean(initializerBean.getKey(), initializerBean.getValue(), beanFactory);}}
}private void addServletContextInitializerBean(String beanName, ServletContextInitializer initializer,ListableBeanFactory beanFactory) {if (initializer instanceof ServletRegistrationBean) {//扫描Servlet到initializers中,"dispatcherServlet"Servlet source = ((ServletRegistrationBean<?>) initializer).getServlet();addServletContextInitializerBean(Servlet.class, beanName, initializer, beanFactory, source);}else if (initializer instanceof FilterRegistrationBean) {//扫描filter到initializers中,Filter source = ((FilterRegistrationBean<?>) initializer).getFilter();addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);}else if (initializer instanceof DelegatingFilterProxyRegistrationBean) {String source = ((DelegatingFilterProxyRegistrationBean) initializer).getTargetBeanName();addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);}else if (initializer instanceof ServletListenerRegistrationBean) {EventListener source = ((ServletListenerRegistrationBean<?>) initializer).getListener();addServletContextInitializerBean(EventListener.class, beanName, initializer, beanFactory, source);}else {addServletContextInitializerBean(ServletContextInitializer.class, beanName, initializer, beanFactory, initializer);}
}

2.1.2 getWebServer()

实际调用的是TomcatServletWebServerFactory[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1PQLKuKR-1668858691871)(E:\一路向北md\开源框架源码分析\SpringBoot\1.jpg)]

上图为 WebServerFactory -> TomcatServletWebServerFactory 的整个类结构关系。

TomcatServletWebServerFactory 是用于获取 Tomcat 作为 WebServer 的工厂类实现,其中最核心的方法就是 getWebServer,获取一个 WebServer 对象实例

/* org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#getWebServer */
public WebServer getWebServer(ServletContextInitializer... initializers) {if (this.disableMBeanRegistry) {Registry.disableRegistry();}//创建tomcat,默认端口8080,new出来Tomcat tomcat = new Tomcat();//创建临时路径,存放tomcat的log日志和dump文件,可用server.tomcat.basedir配置路径File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");tomcat.setBaseDir(baseDir.getAbsolutePath());//创建Connector,协议http1.1,        //Connector.setPort("server.port"),server.port生效的地方//connector编码encoding、SSL(https)支持        //server.xml 中<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />Connector connector = new Connector(this.protocol);connector.setThrowOnFailure(true);tomcat.getService().addConnector(connector);customizeConnector(connector);tomcat.setConnector(connector);        //server.xml 中<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true" />tomcat.getHost().setAutoDeploy(false);        //server.xml 中<Engine name="Catalina" defaultHost="localhost"></Engine>configureEngine(tomcat.getEngine());for (Connector additionalConnector : this.additionalTomcatConnectors) {//添加自定义的Connector到tomcat中,不会有            //server.xml <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />tomcat.getService().addConnector(additionalConnector);}prepareContext(tomcat.getHost(), initializers);return getTomcatWebServer(tomcat);
}

1、customizeConnector : 给 Connector 设置 port、protocolHandler、uriEncoding 等。Connector 构造的逻辑主要是在NIO和APR选择中选择一个协议,然后反射创建实例并强转为 ProtocolHandler

2、prepareContext 这里并不是说准备当前 Tomcat 运行环境的上下文信息,而是准备一个 StandardContext ,也就是准备一个 webapp。

prepareContext()准备 webApplicationContext

对于 Tomcat 来说,每个 context 就是映射到 一个 webapp 的,所以 prepareContext 做的事情就是将 web 应用映射到一个 TomcatEmbeddedContext ,然后加入到 Host 中。

protected void prepareContext(Host host, ServletContextInitializer[] initializers) {File documentRoot = getValidDocumentRoot();// 创建一个 TomcatEmbeddedContext 对象TomcatEmbeddedContext context = new TomcatEmbeddedContext();if (documentRoot != null) {context.setResources(new LoaderHidingResourceRoot(context));}// 设置描述此容器的名称字符串。在属于特定父项的子容器集内,容器名称必须唯一。context.setName(getContextPath());// 设置此Web应用程序的显示名称。context.setDisplayName(getDisplayName());// 设置 webContextPath  默认是   /context.setPath(getContextPath());File docBase = (documentRoot != null) ? documentRoot: createTempDir("tomcat-docbase");context.setDocBase(docBase.getAbsolutePath());// 注册一个FixContextListener监听,这个监听用于设置context的配置状态以及是否加入登录验证的逻辑context.addLifecycleListener(new FixContextListener());// 设置 父 ClassLoadercontext.setParentClassLoader((this.resourceLoader != null) ? this.resourceLoader.getClassLoader(): ClassUtils.getDefaultClassLoader());// 覆盖Tomcat的默认语言环境映射以与其他服务器对齐。resetDefaultLocaleMapping(context);// 添加区域设置编码映射(请参阅Servlet规范2.4的5.4节)addLocaleMappings(context);// 设置是否使用相对地址重定向context.setUseRelativeRedirects(false);try {context.setCreateUploadTargets(true);}catch (NoSuchMethodError ex) {// Tomcat is < 8.5.39. Continue.}configureTldSkipPatterns(context);// 设置 WebappLoader ,并且将 父 classLoader 作为构建参数WebappLoader loader = new WebappLoader(context.getParentClassLoader());// 设置 WebappLoader 的 loaderClass 值loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName());// 会将加载类向上委托loader.setDelegate(true);context.setLoader(loader);if (isRegisterDefaultServlet()) {addDefaultServlet(context);}// 是否注册 jspServletif (shouldRegisterJspServlet()) {addJspServlet(context);addJasperInitializer(context);}context.addLifecycleListener(new StaticResourceConfigurer(context));ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);// 在 host 中 加入一个 context 容器// add时给context注册了个内存泄漏跟踪的监听MemoryLeakTrackingListener,详见 addChild 方法host.addChild(context);//对context做了些设置工作,包括TomcatStarter(实例化并set给context),// LifecycleListener,contextValue,errorpage,Mime,session超时持久化等以及一些自定义工作configureContext(context, initializersToUse);// postProcessContext 方法是空的,留给子类重写用的postProcessContext(context);
}
configureContext(context, initializersToUse);
 protected void configureContext(Context context, ServletContextInitializer[] initializers) {//TomcatStarter实现了容器初始化器接口//把ServletContextInitializer封装为一个容器初始化器TomcatStarter starter = new TomcatStarter(initializers);if (context instanceof TomcatEmbeddedContext) {TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext)context;embeddedContext.setStarter(starter);embeddedContext.setFailCtxIfServletStartFails(true);}//给tomcat的TomcatEmbeddedContext加入一个容器初始化器context.addServletContainerInitializer(starter, NO_CLASSES);/****
}

getTomcatWebServer(tomcat)初始化 TomcatWebServer

在 getWebServer 方法的最后就是构建一个 TomcatWebServer

protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {//创建一个tomcat的web服务,//tomcat服务启动tomcat.start()//创建一个非阻塞守护线程,BIO监听端口8005,接受字节"SHUTDOWN",停止当前tomcatreturn new TomcatWebServer(tomcat, getPort() >= 0);
}public TomcatWebServer(Tomcat tomcat, boolean autoStart) {Assert.notNull(tomcat, "Tomcat Server must not be null");this.tomcat = tomcat;this.autoStart = autoStart;initialize();
}private void initialize() throws WebServerException {logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));synchronized (this.monitor) {try {addInstanceIdToEngineName();//查找context TomcatEmbeddedContextContext context = findContext();context.addLifecycleListener((event) -> {if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {// Remove service connectors so that protocol binding doesn't// happen when the service is started.removeServiceConnectors();}});// tomcat启动this.tomcat.start();rethrowDeferredStartupExceptions();try {ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());}catch (NamingException ex) {}// 与Jetty不同,tomcat线程都是守护线程,我们创建一个阻塞的非守护线程来关闭它。// (守护线程在所有非守护线程关闭后,会自动关闭,所以这里非守护线程最重要的作用是维持tomcat守护线程的运行)。startDaemonAwaitThread();}catch (Exception ex) {stopSilently();destroySilently();throw new WebServerException("Unable to start embedded Tomcat", ex);}}
}

查找 Context ,实际上就是查找一个Tomcat 中的一个 web 应用,SpringBoot 中默认启动一个 Tomcat ,并且一个 Tomcat 中只有一个 Web 应用(FATJAR 模式下,应用与 Tomcat 是 1:1 关系),所有在遍历 Host 下的 Container 时,如果 Container 类型是 Context ,就直接返回了。

private Context findContext() {for (Container child : this.tomcat.getHost().findChildren()) {if (child instanceof Context) {return (Context) child;}}throw new IllegalStateException("The host does not contain a Context");
}
Tomcat 启动过程

在 TomcatWebServer 的 initialize 方法中会执行 tomcat 的启动。

// Start the server to trigger initialization listeners
this.tomcat.start();

org.apache.catalina.startup.Tomcat 的 start 方法:

public void start() throws LifecycleException {// 初始化 servergetServer();// 启动 serverserver.start();
}
初始化 Server

初始化 server 实际上就是构建一个 StandardServer 对象实例,关于 Tomcat 中的 Server 可以参考附件中的说明。

public Server getServer() {// 如果已经存在的话就直接返回if (server != null) {return server;}// 设置系统属性 catalina.useNamingSystem.setProperty("catalina.useNaming", "false");// 直接 new 一个 StandardServerserver = new StandardServer();// 初始化 baseDir (catalina.base、catalina.home、 ~/tomcat.{port})initBaseDir();// Set configuration sourceConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(new File(basedir), null));server.setPort( -1 );Service service = new StandardService();service.setName("Tomcat");server.addService(service);return server;
}

以上就是tomcat的启动流程。

什么时候执行注册的容器初始化器?

tomcat在启动时会启动Context,默认是StandardContext继承了LifecycleBase类,所以会回调startInternal方法,而在startInternal方法中就有一段调用ServletContainerInitializer的onStartup方法:

//org.apache.catalina.core.StandardContext#startInternal
// Call ServletContainerInitializers
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :initializers.entrySet()) {try {//entry.getValue()即 @HandlesTypes中配置的所有类//在此处调用ServletContext,如果为空,则会创建一个//此处回调2.1.1的selfInitializeentry.getKey().onStartup(entry.getValue(),getServletContext());} catch (ServletException e) {log.error(sm.getString("standardContext.sciFail"), e);ok = false;break;}
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-alAsiPDF-1668858691872)(C:\Users\86189\AppData\Roaming\Typora\typora-user-images\1668780688397.png)]

//创建SevletContext的过程
//org.apache.catalina.core.StandardContext#getServletContext
public ServletContext getServletContext() {if (this.context == null) {this.context = new ApplicationContext(this);if (this.altDDName != null) {this.context.setAttribute("org.apache.catalina.deploy.alt_dd", this.altDDName);}}return this.context.getFacade();
}

小结

① tomcat嵌入:是AbstactApplicationContext.onRefresh()中启动的

Tomcat tomcat = new Tomcat();
//server.xml的属性设置到tomcat
//包括server.port设置到Connector中
tomcat.start()

start启动会执行selfInitialize(),tomcat–dispatcherServlet–springbootIOC容器,的绑定都是在selfInitalize()里面实现的

① ServletContext.setAttribute(“rootApplicationContext”,springbootApplicationContext)

② 扫描beanfactory中的ServletContextInitializer.class类型Servlet/Filter

③ DispatcherServletRegistrationBean中dispatcherServlet放入了tomcat中

④ dispatcherServlet.init()时

new WebApplicationContext().setParent(servletContext.getAttribute(“rootApplicationContext”))

②tomcat.start()启动是一个监听8080端口的守护线程(非阻塞NIO),后面启动了一个非守护线程监听8005端口(阻塞BIO),作用:

  • 当守护线程关闭后,非守护线程会全部关闭。开一个非守护线程(8005),可维持守护线程(8080)一直运行。

  • 非守护线程(8005)还会接受"SHUTDOWN”字节指令,关闭非守护线程,从而关闭守护线程

    主线程是非守护线程,其他单独创建的线程或者线程池创建的工作线程都默认是守护线程

当Tomcat启动时,做完所有初始化和启动工作,主线程会进入一个无限循环监听默认8005端口的状态,直到网络中读取到SHUTDOWN指令,才会退出循环,进而调用Tomcat停止销毁操作。

对 SpringBoot 中内嵌 Tomcat 的过程做了分析,这个过程实际上并不复杂,就是在刷新 Spring 上下文的过程中将 Tomcat 容器启动起来,并且将当前应用绑定到一个 Context ,然后添加了 Host。下图是程序的执行堆栈和执行内嵌 Tomcat 初始化和启动的时机。

下面总结下整个过程:

  • 通过自定配置注册相关的 Bean ,包括一些 Factory 和 后置处理器等
  • 上下文刷新阶段,执行创建 WebServer,这里需要用到前一个阶段所注册的 Bean
    • 包括创建 ServletContext
    • 实例化 webServer
  • 创建 Tomcat 实例、创建 Connector 连接器
  • 绑定 应用到 ServletContext,并添加相关的生命周期范畴内的监听器,然后将 Context 添加到 host 中
  • 实例化 webServer 并且启动 Tomcat 服务

SpringBoot 的 Fatjar 方式没有提供共享 Tomcat 的实现逻辑,就是两个 FATJAT 启动可以只实例化一个 Tomcat 实例(包括 Connector 和 Host ),从前面的分析知道,每个 web 应用(一个 FATJAT 对应的应用)实例上就是映射到一个 Context ;而对于 war 方式,一个 Host 下面是可以挂载多个 Context 的。

附:

selfInitialize方法中对DispatcherServletRegistrationBean的处理onStartup的调用

//org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#selfInitialize
private void selfInitialize(ServletContext servletContext) throws ServletException {this.prepareWebApplicationContext(servletContext);this.registerApplicationScope(servletContext);WebApplicationContextUtils.registerEnvironmentBeans(this.getBeanFactory(), servletContext);Iterator var2 = this.getServletContextInitializerBeans().iterator();while(var2.hasNext()) {ServletContextInitializer beans = (ServletContextInitializer)var2.next();beans.onStartup(servletContext);}}
//org.springframework.boot.web.servlet.RegistrationBean#onStartup
public final void onStartup(ServletContext servletContext) throws ServletException {String description = this.getDescription();if (!this.isEnabled()) {logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");} else {this.register(description, servletContext);}
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VU2jNjgR-1668858691874)(C:\Users\86189\AppData\Roaming\Typora\typora-user-images\1668743095572.png)]

将DispatchServlet(已经被Spring管理的bean)加入ServletContext

//org.springframework.boot.web.servlet.DynamicRegistrationBean#register
protected final void register(String description, ServletContext servletContext) {D registration = this.addRegistration(description, servletContext);if (registration == null) {logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)");} else {this.configure(registration);}
}//org.springframework.boot.web.servlet.ServletRegistrationBean#addRegistration
//官网的示例也是走到这里
protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {String name = this.getServletName();//DispatcherServletRegistrationBean的Servlet来自自动配置的注入return servletContext.addServlet(name, this.servlet);
}

configure

负责添加mapping和启动顺序等

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bGsYjlWe-1668858691874)(C:\Users\86189\AppData\Roaming\Typora\typora-user-images\1668743335675.png)]

DispatcherServletRegistrationBean

public class DispatcherServletRegistrationBean extends ServletRegistrationBean<DispatcherServlet> implements DispatcherServletPath {private final String path;//SpringBoot自动配置传入Servletpublic DispatcherServletRegistrationBean(DispatcherServlet servlet, String path) {super(servlet, new String[0]);Assert.notNull(path, "Path must not be null");this.path = path;super.addUrlMappings(new String[]{this.getServletUrlMapping()});}public String getPath() {return this.path;}public void setUrlMappings(Collection<String> urlMappings) {throw new UnsupportedOperationException("URL Mapping cannot be changed on a DispatcherServlet registration");}public void addUrlMappings(String... urlMappings) {throw new UnsupportedOperationException("URL Mapping cannot be changed on a DispatcherServlet registration");}
}

DispatcherServletRegistrationBean什么时候初始化?

SpringBoot自动配置完成

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PVCFIbFp-1668858691875)(E:\一路向北md\开源框架源码分析\SpringBoot\1668741980863.png)]

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {/** The bean name for a DispatcherServlet that will be mapped to the root URL "/"*/public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";/** The bean name for a ServletRegistrationBean for the DispatcherServlet "/"*/public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";@Configuration(proxyBeanMethods = false)@Conditional(DefaultDispatcherServletCondition.class)@ConditionalOnClass(ServletRegistration.class)@EnableConfigurationProperties({ HttpProperties.class, WebMvcProperties.class })protected static class DispatcherServletConfiguration {//注入dispatcherServlet bean实例@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)public DispatcherServlet dispatcherServlet(HttpProperties httpProperties, WebMvcProperties webMvcProperties) {// 创建dispatcherServlet实例,设置dispatcherServlet的属性,// WebMvcProperties对应application.properties中的"spring.mvc.dispatchOptionsRequest"// spring.mvc.dispatch_options_request = true// spring.mvc.dispatch_trace_request = false// spring.mvc.throw_exception_if_no_handler_found = false// spring.mvc.publish_request_handled_events = true// spring.http.log_request_details = falseDispatcherServlet dispatcherServlet = new DispatcherServlet();dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());dispatcherServlet.setEnableLoggingRequestDetails(httpProperties.isLogRequestDetails());return dispatcherServlet;}@Bean@ConditionalOnBean(MultipartResolver.class)@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)public MultipartResolver multipartResolver(MultipartResolver resolver) {// Detect if the user has created a MultipartResolver but named it incorrectlyreturn resolver;}}@Configuration(proxyBeanMethods = false)@Conditional(DispatcherServletRegistrationCondition.class)@ConditionalOnClass(ServletRegistration.class)@EnableConfigurationProperties(WebMvcProperties.class)@Import(DispatcherServletConfiguration.class)protected static class DispatcherServletRegistrationConfiguration {@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {//封装dispatcherServlet实例,//spring.mvc.servlet.path = ///spring.mvc.servlet.load-on-startup = -1//multipartConfgDispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,webMvcProperties.getServlet().getPath());registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());multipartConfig.ifAvailable(registration::setMultipartConfig);return registration;}}//.......
}

name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
WebMvcProperties webMvcProperties, ObjectProvider multipartConfig) {
//封装dispatcherServlet实例,
//spring.mvc.servlet.path = /
//spring.mvc.servlet.load-on-startup = -1
//multipartConfg
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
webMvcProperties.getServlet().getPath());
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
multipartConfig.ifAvailable(registration::setMultipartConfig);
return registration;
}

}
//.......

}


SpringBoot内嵌Tomcat原理相关推荐

  1. 总结:SpringBoot内嵌Tomcat原理

    一.介绍 一般我们启动web服务都需要单独的去安装tomcat,而Springboot自身却直接整合了Tomcat,什么原理呢? 二.原理 SpringBoot应用只需要引入spring-boot-s ...

  2. Springboot内嵌tomcat

    Springboot内嵌tomcat 前言 一.依赖引入starter-web 二.源码截图 tomcat 如何内嵌 三.springboot 如何启动tomcat 前言 一.依赖引入starter- ...

  3. 指定SpringBoot内嵌Tomcat的版本,修复(CVE-2021-42340)漏洞

    1.Tomcat漏洞 近日,Apache Tomcat 发布安全更新,更新了一处拒绝服务漏洞(CVE-2021-42340).攻击者可以通过该漏洞进行拒绝服务攻击.建议广大用户及时升级至最新版本. 1 ...

  4. tomcat start 无法启动_解密Springboot内嵌Tomcat

    Springboot简介 相信大多数开发者对Springboot比较熟悉了,它能够快速地创建一个spring应用,能够完全摒弃XML的配置方式,并且内嵌了Tomcat.Jetty这样的Servlet容 ...

  5. 解密Springboot内嵌Tomcat

    Springboot简介 相信大多数开发者对Springboot比较熟悉了,它能够快速地创建一个spring应用,能够完全摒弃XML的配置方式,并且内嵌了Tomcat.Jetty这样的Servlet容 ...

  6. springboot内嵌tomcat调优

    在springboot-configuration-metadata.json文件下面,有很多属于springboot得喷子hi属性,以下为tomcat的默认配置属性: server.tomcat.a ...

  7. springboot内嵌Tomcat启动失败

    问题描述 开发需求期间引入友军的二方包,导致服务启动失败,失败日志如下 java.lang.reflect.InvocationTargetExceptionat sun.reflect.Native ...

  8. springboot内嵌tomcat的jar包启动流程

    白茶清欢无别事,我在等风也等你.苦酒折柳今相离,无风无月也无你 调用SpringApplication.run org.springframework.boot.SpringApplication#r ...

  9. EmbeddedServletContainerException: Unable to start embedded Tomcat 内嵌Tomcat启动失败

    这个问题很莫名其妙,早上项目运行的还好好的,下午再跑起来就直接报错了 爆粗信息大概就是项目启动的时候会刷新容器时在创建内嵌servlet容器的时候出错了 在网上搜了很多方法,有这么几种说法 1.端口被 ...

最新文章

  1. 用IE重起计算机或者关机
  2. mongodb 3.0.x 添加用户名和密码设置权限
  3. C++ Primer 5th笔记(chap 13 拷贝控制)三五法则
  4. python中Dict与OrderedDict
  5. linux设置NO_PROXY绕过代理
  6. django框架中表单
  7. PS教程第二十五课:自由选区
  8. 安装codeblocks和wxwidgets及opencv
  9. css 总结内容用到的绝对居中的几种方式
  10. TPLINK-WR720N刷openwrt
  11. 【特别版】计算机哲学对学习生活借鉴的几个例子
  12. [转]HTTP 错误 403.1 - 禁止访问:执行访问被拒绝。
  13. 我设计了一个支撑数亿用户的系统
  14. 如何在页面打开后根据条件让EditorGridPanel的某一行的复选框默认选中?
  15. idea社区版 html,利用IntelliJ IDEA社区版开发servlet
  16. 【高数】如何由解倒求微分方程?及微分方程的阶数、任意常数、特征根的关系
  17. Email,电子邮箱免费注册流程
  18. 天堂里有没有车来车往
  19. 康佳在埃及成立合资公司发力非洲中东市场
  20. ssm基于微信小程序的游泳馆管理系统 uinapp 计算机毕业设计

热门文章

  1. 电商平台京东商品详情接口调用展示
  2. Arduino基础入门篇11—光控灯
  3. 10个程序员需要收藏的良心网站,你绝对没有用过
  4. 温故而知新:设计模式之原型模式(Prototype)
  5. Linux安装多功能词典GoldenDict
  6. 使用Kubeadm部署K8S单节点,速度快于二进制部署
  7. Linux用户管理篇
  8. Docker学习第一课 -- docker简介和和学习路线
  9. android红包雨动画,SurfaceView实现红包雨平移动画
  10. 【AXI】解读AXI协议双向握手机制的原理