• 【springboot】自动整合Tomcat原理


    通过前面我们会SpringBoot的自动配置机制、Starter机制、启动过程的底层分析,我们拿一个实际的业务案例来串讲一下,那就是SpringBoot和Tomcat的整合。

    我们知道,只要我们的项目添加的starter为:spring-boot-starter-web,那么我们启动项目时,SpringBoot就会自动启动一个Tomcat。

    那么这是怎么做到的呢?

    自动配置类ServletWebServerFactoryAutoConfiguration

    首先我们可以发现,在spring-boot-starter-web这个starter中,只是简单的引入了spring-boot-starter-tomcat这个starter,这个spring-boot-starter-tomcat又引入了tomcat-embed-core依赖,所以只要我们项目中依赖了spring-boot-starter-web就相当于依赖了Tomcat。

    然后在SpringBoot众多的自动配置类中,有一个自动配置类叫做ServletWebServerFactoryAutoConfiguration,定义为:

    @Configuration(proxyBeanMethods = false)
    @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
    @ConditionalOnClass(ServletRequest.class)
    @ConditionalOnWebApplication(type = Type.SERVLET)
    @EnableConfigurationProperties(ServerProperties.class)
    @Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
    ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
    ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
    ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
    public class ServletWebServerFactoryAutoConfiguration {
    // ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    首先看这个自动配置类所需要的条件:

    1. @ConditionalOnClass(ServletRequest.class):表示项目依赖中要有ServletRequest类(server api)
    2. @ConditionalOnWebApplication(type = Type.SERVLET):表示项目应用类型得是SpringMVC

    在上面提到的spring-boot-starter-web中,其实还间接的引入了spring-web、spring-webmvc等依赖,这就使得第二个条件满足,而对于第一个条件的ServletRequest类,虽然它是Servlet规范中的类,但是在我们所依赖的tomcat-embed-core这个jar包中是存在这个类的,这是因为Tomcat在自己的源码中把Servlet规范中的一些代码也包含进去了,比如:

    在这里插入图片描述

    这就使得ServletWebServerFactoryAutoConfiguration这个自动配置的两个条件都符合,那么Spring就能去解析它,一解析它就发现这个自动配置类Import进来了三个类:

    1. ServletWebServerFactoryConfiguration.EmbeddedTomcat.class
    2. ServletWebServerFactoryConfiguration.EmbeddedJetty.class
    3. ServletWebServerFactoryConfiguration.EmbeddedUndertow.class

    EmbeddedTomcat

    很明显,Import进来的这三个类应该是差不多,我们看EmbeddedTomcat这个类:

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
    @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
    static class EmbeddedTomcat {
    
    	@Bean
    	TomcatServletWebServerFactory tomcatServletWebServerFactory(
    		ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
    		ObjectProvider<TomcatContextCustomizer> contextCustomizers,
    		ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
    		TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
    		factory.getTomcatConnectorCustomizers()
    			.addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
    		factory.getTomcatContextCustomizers()
    			.addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
    		factory.getTomcatProtocolHandlerCustomizers()
    			.addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
    		return factory;
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    可以发现这个类是一个配置类,所以Spring也会来解析它,不过它也有两个条件:

    1. @ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class }):项目依赖中要有Servlet.class、Tomcat.class、UpgradeProtocol.class这三个类,这个条件比较容易理解,项目依赖中有Tomcat的类,那这个条件就符合。
    2. @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT),项目中没有ServletWebServerFactory类型的Bean,因为这个配置类的内部就是定义了一个TomcatServletWebServerFactory类型的Bean,TomcatServletWebServerFactory实现了ServletWebServerFactory接口,所以这个条件注解的意思就是,如果程序员自己没有定义ServletWebServerFactory类型的Bean,那么就符合条件,不然,如果程序员自己定义了ServletWebServerFactory类型的Bean,那么条件就不符合,也就导致SpringBoot给我们定义的TomcatServletWebServerFactory这个Bean就不会生效,最终生效的就是程序员自己定义的。

    所以,通常只要我们项目依赖中有Tomcat依赖,那就符合条件,那最终Spring容器中就会有TomcatServletWebServerFactory这个Bean。

    对于另外的EmbeddedJetty和EmbeddedUndertow,也差不多,都是判断项目依赖中是否有Jetty和Undertow的依赖,如果有,那么对应在Spring容器中就会存在JettyServletWebServerFactory类型的Bean、或者存在UndertowServletWebServerFactory类型的Bean。

    总结一下:

    1. 有Tomcat依赖,就有TomcatServletWebServerFactory这个Bean
    2. 有Jetty依赖,就有JettyServletWebServerFactory这个Bean
    3. 有Undertow依赖,就有UndertowServletWebServerFactory这个Bean

    那么SpringBoot给我们配置的这几个Bean到底有什么用呢?

    TomcatServletWebServerFactory

    我们前面说到,TomcatServletWebServerFactory实现了ServletWebServerFactory这个接口,这个接口的定义为:

    @FunctionalInterface
    public interface ServletWebServerFactory {
    	WebServer getWebServer(ServletContextInitializer... initializers);
    }
    
    public interface WebServer {
    
    	void start() throws WebServerException;
    
    	void stop() throws WebServerException;
    
    	int getPort();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    我们发现ServletWebServerFactory其实就是用来获得WebServer对象的,而WebServer拥有启动、停止、获取端口等方法,那么很自然,我们就发现WebServer其实指的就是Tomcat、Jetty、Undertow,而TomcatServletWebServerFactory就是用来生成Tomcat所对应的WebServer对象,具体一点就是TomcatWebServer对象,并且在生成TomcatWebServer对象时会把Tomcat给启动起来,在源码中,调用TomcatServletWebServerFactory对象的getWebServer()方法时就会启动Tomcat。

    我们再来看TomcatServletWebServerFactory这个Bean的定义:

    @Bean
    TomcatServletWebServerFactory tomcatServletWebServerFactory(
    	ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
    	ObjectProvider<TomcatContextCustomizer> contextCustomizers,
    	ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
    	TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
    	factory.getTomcatConnectorCustomizers()
    		.addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
    	factory.getTomcatContextCustomizers()
    		.addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
    	factory.getTomcatProtocolHandlerCustomizers()
    		.addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
    	return factory;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    要构造这个Bean,Spring会从Spring容器中获取到TomcatConnectorCustomizer、TomcatContextCustomizer、TomcatProtocolHandlerCustomizer这三个类型的Bean,然后把它们添加到TomcatServletWebServerFactory对象中去,很明显这三种Bean是用来配置Tomcat的,比如:

    1. TomcatConnectorCustomizer:是用来配置Tomcat中的Connector组件的
    2. TomcatContextCustomizer:是用来配置Tomcat中的Context组件的
    3. TomcatProtocolHandlerCustomizer:是用来配置Tomcat中的ProtocolHandler组件的

    也就是我们可以通过定义TomcatConnectorCustomizer类型的Bean,来对Tomcat进行配置,比如:

    package com.morris.spring.boot;
    
    import org.apache.catalina.connector.Connector;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
    import org.springframework.context.annotation.Bean;
    
    @SpringBootApplication
    public class SpringBootDemoApplication {
    
    	@Bean
    	public TomcatConnectorCustomizer tomcatConnectorCustomizer(){
    		return new TomcatConnectorCustomizer() {
    			@Override
    			public void customize(Connector connector) {
    				connector.setPort(8888);
    			}
    		};
    	}
    
        public static void main(String[] args) {
            SpringApplication.run(SpringBootDemoApplication.class, args);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    这样Tomcat就会绑定8888这个端口。

    ServletWebServerFactory的调用

    有了TomcatServletWebServerFactory这个Bean之后,在SpringBoot的启动过程中,会执行ServletWebServerApplicationContext的onRefresh()方法,而这个方法会调用createWebServer()方法:

    private void createWebServer() {
    	WebServer webServer = this.webServer;
    	ServletContext servletContext = getServletContext();
    	if (webServer == null && servletContext == null) {
    		StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
    		// 获取TomcatServletWebServerFactory
    		ServletWebServerFactory factory = getWebServerFactory();
    		createWebServer.tag("factory", factory.getClass().toString());
    		// 启动tomcat
    		this.webServer = factory.getWebServer(getSelfInitializer());
    		createWebServer.end();
    		getBeanFactory().registerSingleton("webServerGracefulShutdown",
    										   new WebServerGracefulShutdownLifecycle(this.webServer));
    		getBeanFactory().registerSingleton("webServerStartStop",
    										   new WebServerStartStopLifecycle(this, this.webServer));
    	}
    	else if (servletContext != null) {
    		try {
    			getSelfInitializer().onStartup(servletContext);
    		}
    		catch (ServletException ex) {
    			throw new ApplicationContextException("Cannot initialize servlet context", ex);
    		}
    	}
    	initPropertySources();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    很明显,getWebServerFactory()负责获取具体的ServletWebServerFactory对象,要么是TomcatServletWebServerFactory对象,要么是JettyServletWebServerFactory对象,要么是UndertowServletWebServerFactory对象,注意只能获取到一个,然后调用该对象的getWebServer方法,启动对应的Tomcat、或者Jetty、或者Undertow。

    getWebServerFactory方法中的逻辑比较简单,获取Spring容器中的ServletWebServerFactory类型的Bean对象,如果没有获取到则抛异常,如果找到多个也抛异常,也就是在Spring容器中只能有一个ServletWebServerFactory类型的Bean对象。

    拿到TomcatServletWebServerFactory对象后,就调用它的getWebServer方法,而在这个方法中就会生成一个Tomcat对象,并且利用前面的TomcatConnectorCustomizer等等会Tomcat对象进行配置,最后启动Tomcat。

    这样在启动应用时就完成了Tomcat的启动,到此我们通过这个案例也看到了具体的Starter机制、自动配置的具体使用。

    将DispatherServlet和Filter注入到Web容器

    private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
    	return this::selfInitialize;
    }
    
    private void selfInitialize(ServletContext servletContext) throws ServletException {
    	// 将servletContext与spring容器关联
    	prepareWebApplicationContext(servletContext);
    	registerApplicationScope(servletContext);
    	// 将servletContext的初始化参数注入到spring容器中
    	WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
    	for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
    		/**
    			 * DispatcherServletRegistrationBean是在DispatcherServletAutoConfiguration中注入到spring中
    			 *
    			 * DispatcherServletRegistrationBean负责将DispatcherServlet注入到tomcat
    			 * FilterRegistrationBean负责将Filter注入到tomcat
    			 *
    			 * @see org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean
    			 * @see org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean
    			 * @see FilterRegistrationBean
    			 */
    		beans.onStartup(servletContext);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    ServletWebServerFactoryAutoConfiguration

    我们前面提到了我们可以利用TomcatConnectorCustomizer对Tomca中的Connector组件进行配置,我们可能会想到默认情况下,SpringBoot是不是就是提供了一个TomcatConnectorCustomizer的Bean,然后给Connector配置了8080端口,或者从Environment对象中获取server.port配置,并设置到Connector中去呢?

    并不是,因为如果SpringBoot这么实现,那么默认就得提供三个ConnectorCustomizer的Bean,一个TomcatConnectorCustomizer、一个JettyConnectorCustomizer、一个UndertowConnectorCustomizer,这是比较不恰当的,我们知道默认情况下,不管我们是用Tomcat,还是Jetty,还是Undertow,它们启动时绑定的都是8080端口,也就是说SpringBoot并不会根据不同的WebServer设置不同的端口,也就是说SpringBoot只会给WebServer设置端口,而不会区分WebServer的不同实现。

    所以在自动配置类ServletWebServerFactoryAutoConfiguration中,会定义一个ServletWebServerFactoryCustomizer类型的Bean,定义为:

    @Bean
    public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties,
    																		   ObjectProvider<WebListenerRegistrar> webListenerRegistrars,
    																		   ObjectProvider<CookieSameSiteSupplier> cookieSameSiteSuppliers) {
    	return new ServletWebServerFactoryCustomizer(serverProperties,
    												 webListenerRegistrars.orderedStream().collect(Collectors.toList()),
    												 cookieSameSiteSuppliers.orderedStream().collect(Collectors.toList()));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这个Bean会接收一个ServerProperties的Bean,ServerProperties的Bean对应的就是properties文件中前缀为server的配置,我们可以利用ServerProperties对象的getPort方法获取到我们所配置的server.port的值。

    而ServletWebServerFactoryCustomizer是针对一个ServletWebServerFactory的自定义器,也就是用来配置TomcatServletWebServerFactory这个Bean的,到时候ServletWebServerFactoryCustomizer就会利用ServerProperties对象来对TomcatServletWebServerFactory对象进行设置。

    在ServletWebServerFactoryAutoConfiguration这个自动配置上,除开Import了EmbeddedTomcat、EmbeddedJetty、EmbeddedUndertow这三个配置类,还Import了一个ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,这个BeanPostProcessorsRegistrar会向Spring容器中注册一个WebServerFactoryCustomizerBeanPostProcessor类型的Bean。

    WebServerFactoryCustomizerBeanPostProcessor是一个BeanPostProcessor,它专门用来处理类型为WebServerFactory的Bean对象,而我们的TomcatServletWebServerFactory、JettyServletWebServerFactory、UndertowServletWebServerFactory也都实现了这个接口,所以不管当前项目依赖的情况,只要在Spring在创建比如TomcatServletWebServerFactory这个Bean时,WebServerFactoryCustomizerBeanPostProcessor就会对它进行处理,处理的逻辑为:

    1. 从Spring容器中拿到WebServerFactoryCustomizer类型的Bean,也就是前面说的ServletWebServerFactoryCustomizer对象
    2. 然后调用ServletWebServerFactoryCustomizer对象的customize方法,把TomcatServletWebServerFactory对象传入进去
    3. customize方法中就会从ServerProperties对象获取各种配置,然后设置给TomcatServletWebServerFactory对象

    org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor#postProcessBeforeInitialization(org.springframework.boot.web.server.WebServerFactory)

    @SuppressWarnings("unchecked")
    private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
    	LambdaSafe.callbacks(WebServerFactoryCustomizer.class, getCustomizers(), webServerFactory)
    		.withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
    		.invoke((customizer) -> customizer.customize(webServerFactory));
    }
    
    private Collection<WebServerFactoryCustomizer<?>> getCustomizers() {
    	if (this.customizers == null) {
    		// Look up does not include the parent context
    		this.customizers = new ArrayList<>(getWebServerFactoryCustomizerBeans());
    		this.customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
    		this.customizers = Collections.unmodifiableList(this.customizers);
    	}
    	return this.customizers;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    调用ServletWebServerFactoryCustomizer对象的customize方法:

    org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryCustomizer#customize

    @Override
    public void customize(ConfigurableServletWebServerFactory factory) {
    	PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
    	map.from(this.serverProperties::getPort).to(factory::setPort);
    	map.from(this.serverProperties::getAddress).to(factory::setAddress);
    	map.from(this.serverProperties.getServlet()::getContextPath).to(factory::setContextPath);
    	map.from(this.serverProperties.getServlet()::getApplicationDisplayName).to(factory::setDisplayName);
    	map.from(this.serverProperties.getServlet()::isRegisterDefaultServlet).to(factory::setRegisterDefaultServlet);
    	map.from(this.serverProperties.getServlet()::getSession).to(factory::setSession);
    	map.from(this.serverProperties::getSsl).to(factory::setSsl);
    	map.from(this.serverProperties.getServlet()::getJsp).to(factory::setJsp);
    	map.from(this.serverProperties::getCompression).to(factory::setCompression);
    	map.from(this.serverProperties::getHttp2).to(factory::setHttp2);
    	map.from(this.serverProperties::getServerHeader).to(factory::setServerHeader);
    	map.from(this.serverProperties.getServlet()::getContextParameters).to(factory::setInitParameters);
    	map.from(this.serverProperties.getShutdown()).to(factory::setShutdown);
    	for (WebListenerRegistrar registrar : this.webListenerRegistrars) {
    		registrar.register(factory);
    	}
    	if (!CollectionUtils.isEmpty(this.cookieSameSiteSuppliers)) {
    		factory.setCookieSameSiteSuppliers(this.cookieSameSiteSuppliers);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    这样当TomcatServletWebServerFactory这个Bean对象创建完成后,它里面的很多属性,比如port,就已经是程序员所配置的值了,后续执行getWebServer方法时,就直接获取自己的属性,比如port属性,设置给Tomcat,然后再利用TomcatConnectorCustomizer等进行处理,最后启动Tomcat。

    到此,SpringBoot整合Tomcat的核心原理就分析完了,主要涉及的东西有:

    1. spring-boot-starter-web:会自动引入Tomcat、SpringMVC的依赖
    2. ServletWebServerFactoryAutoConfiguration:自动配置类
    3. ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar:用来注册WebServerFactoryCustomizerBeanPostProcessor
    4. ServletWebServerFactoryConfiguration.EmbeddedTomcat:配置TomcatServletWebServerFactory
    5. ServletWebServerFactoryConfiguration.EmbeddedJetty:配置JettyServletWebServerFactory
    6. ServletWebServerFactoryConfiguration.EmbeddedUndertow:配置UndertowServletWebServerFactory
    7. ServletWebServerFactoryCustomizer:用来配置ServletWebServerFactory
    8. WebServerFactoryCustomizerBeanPostProcessor:是一个BeanPostProcessor,利用ServletWebServerFactoryCustomizer来配置ServletWebServerFactory
    9. ServletWebServerApplicationContext中的onRefresh()方法:负责启动Tomcat
  • 相关阅读:
    AWS认证SAA-C03每日一题
    RSA中PCKS1, PSS and OAEP的区别
    速卖通、lazada卖家怎么利用自养号测评让店铺排名靠前?
    【ElfBoard】基于 Linux 的智能家居小项目
    大型数据集处理之道:深入了解Hadoop及MapReduce原理
    Java8实战-总结33
    Vitis HLS 学习笔记--HLS入门示例集合-目录
    .NET混合开发解决方案5 WebView2运行时与分发应用
    【python自动化】02. pywin32库自动操作键鼠(保姆级代码注释)
    linux 修改静态ip立即生效,无需重启
  • 原文地址:https://blog.csdn.net/u022812849/article/details/126750170