NOTE

2.40 原理分析

1. 开启ServletContainer自动配置 - @EmbeddedServletContainerAutoConfiguration EmbeddedServletContainerAutoConfiguration是spring boot自动配置嵌入式的servlet容器的自动配置类 他会

Java创建于 更新于 historical

这是历史学习笔记,可能存在过时或不完整的理解。

1. 开启ServletContainer自动配置

  • @EmbeddedServletContainerAutoConfiguration EmbeddedServletContainerAutoConfiguration是spring boot自动配置嵌入式的servlet容器的自动配置类
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)// 导入了BeanPostProcessorsRegistrar,这个类的作用在于修改tomcat的配置是如何生效的
public class EmbeddedServletContainerAutoConfiguration {

	@Configuration
	@ConditionalOnClass({ Servlet.class, Tomcat.class })//Tomcat.class存在的情况下生效--使用Tomcat
	@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class,
			search = SearchStrategy.CURRENT)//用户没有自定义EmbeddedServletContainerFactory的情况下生效
	public static class EmbeddedTomcat {

		//先spring容器注入一个tomcat工厂
		@Bean
		public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
			return new TomcatEmbeddedServletContainerFactory();
		}

	}

	@Configuration
	@ConditionalOnClass({ Servlet.class, Server.class, Loader.class,
			WebAppContext.class })//Loader.class存在的情况下生效--使用Jetty
	@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class,
			search = SearchStrategy.CURRENT)
	public static class EmbeddedJetty {

		@Bean
		public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {
			return new JettyEmbeddedServletContainerFactory();
		}

	}
}

他会根据classpath存在的tomcat、jetty来决定生成那种servlet容器 我们以tomcat的TomcatEmbeddedServletContainerFactory为例

2. 注入Tomcat容器工厂

2.1. Tomcat容器工厂是怎样的

  • 创建Tomcat
public class TomcatEmbeddedServletContainerFactory
		extends AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware {

		@Override
		public EmbeddedServletContainer getEmbeddedServletContainer(
				ServletContextInitializer... initializers) {
			//创建tomcat
			Tomcat tomcat = new Tomcat();
			//以下为配置tomcat基本属性

			//基础目录:没有设置的话创建临时目录
			File baseDir = (this.baseDirectory != null) ? this.baseDirectory
					: createTempDir("tomcat");
			tomcat.setBaseDir(baseDir.getAbsolutePath());

			//http连接池
			Connector connector = new Connector(this.protocol);
			tomcat.getService().addConnector(connector);

			//自定义配置的关键方法
			customizeConnector(connector);

			tomcat.setConnector(connector);
			tomcat.getHost().setAutoDeploy(false);
			configureEngine(tomcat.getEngine());
			for (Connector additionalConnector : this.additionalTomcatConnectors) {
				tomcat.getService().addConnector(additionalConnector);
			}
			prepareContext(tomcat.getHost(), initializers);

			//new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0);
			return getTomcatEmbeddedServletContainer(tomcat);
		}

		protected void customizeConnector(Connector connector) {
			int port = (getPort() >= 0) ? getPort() : 0;
			connector.setPort(port);
			if (StringUtils.hasText(this.getServerHeader())) {
				connector.setAttribute("server", this.getServerHeader());
			}
			if (connector.getProtocolHandler() instanceof AbstractProtocol) {
				customizeProtocol((AbstractProtocol<?>) connector.getProtocolHandler());
			}
			if (getUriEncoding() != null) {
				connector.setURIEncoding(getUriEncoding().name());
			}

			// If ApplicationContext is slow to start we want Tomcat not to bind to the socket
			// prematurely...
			connector.setProperty("bindOnInit", "false");

			if (getSsl() != null && getSsl().isEnabled()) {
				customizeSsl(connector);
			}
			if (getCompression() != null && getCompression().getEnabled()) {
				customizeCompression(connector);
			}
			//获取所有TomcatConnectorCustomizer,调用customize方法配置
			//ServerProperties就是其中一个
			for (TomcatConnectorCustomizer customizer : this.tomcatConnectorCustomizers) {
				customizer.customize(connector);
			}
		}
}

TomcatEmbeddedServletContainerFactory的作用时创建tomcat容器,他的关键方法是getEmbeddedServletContainer,那么这个方法是什么时候调用的呢?

2.2. Tomcat是何时创建的

2.2.1. Web环境下刷新容器

我们知道spring context refresh的时候有一个步骤是onRefresh,他是一个模板方法,交由子类实现。 在web环境下会调用EmbeddedWebApplicationContext的onRefresh方法

  • EmbeddedWebApplicationContext onRefresh
protected void onRefresh() {
		super.onRefresh();
	try {
		//创建servlet容器
		createEmbeddedServletContainer();
	}
	catch (Throwable ex) {
		throw new ApplicationContextException("Unable to start embedded container",
				ex);
	}
}

private void createEmbeddedServletContainer() {
	//第一次进来这两个都为空
	EmbeddedServletContainer localContainer = this.embeddedServletContainer;
	ServletContext localServletContext = getServletContext();
	if (localContainer == null && localServletContext == null) {
		//获取servlet容器工厂,会调用@Bean TomcatEmbeddedServletContainerFactory
		EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
		//通过工厂创建servlet容器
		this.embeddedServletContainer = containerFactory
				//调用TomcatEmbeddedServletContainerFactory#getEmbeddedServletContainer
				.getEmbeddedServletContainer(getSelfInitializer());
	}
	else if (localServletContext != null) {
		try {
			getSelfInitializer().onStartup(localServletContext);
		}
		catch (ServletException ex) {
			throw new ApplicationContextException("Cannot initialize servlet context",
					ex);
		}
	}
	initPropertySources();
}

2.3. Tomcat创建过程

  • TomcatEmbeddedServletContainer
public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer {

	public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) {
		Assert.notNull(tomcat, "Tomcat Server must not be null");
		this.tomcat = tomcat;
		this.autoStart = autoStart;
		//初始化tomcat
		initialize();
	}

	private void initialize() throws EmbeddedServletContainerException {
		TomcatEmbeddedServletContainer.logger
				.info("Tomcat initialized with port(s): " + getPortsDescription(false));
		synchronized (this.monitor) {
			try {
				addInstanceIdToEngineName();
				try {
					final Context context = findContext();
					context.addLifecycleListener(new LifecycleListener() {

						@Override
						public void lifecycleEvent(LifecycleEvent 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服务器
					// Start the server to trigger initialization listeners
					this.tomcat.start();

					// We can re-throw failure exception directly in the main thread
					rethrowDeferredStartupExceptions();

					try {
						ContextBindings.bindClassLoader(context, getNamingToken(context),
								getClass().getClassLoader());
					}
					catch (NamingException ex) {
						// Naming is not enabled. Continue
					}

					// Unlike Jetty, all Tomcat threads are daemon threads. We create a
					// blocking non-daemon to stop immediate shutdown
					startDaemonAwaitThread();
				}
				catch (Exception ex) {
					containerCounter.decrementAndGet();
					throw ex;
				}
			}
			catch (Exception ex) {
				stopSilently();
				throw new EmbeddedServletContainerException(
						"Unable to start embedded Tomcat", ex);
			}
		}
	}
}

除了注入servlet容器之外,还导入一个BeanPostProcessorsRegistrar类,他是自定义配置的关键

3. 自定义配置生效的原理

EmbeddedServletContainerAutoConfiguration除了创建并启动tomcat容器外,还导入一个关键类BeanPostProcessorsRegistrar

3.1. 注入BeanPostProcessorsRegistrar用于自定义tomcat配置

public static class BeanPostProcessorsRegistrar
		implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
			@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
			BeanDefinitionRegistry registry) {
		if (this.beanFactory == null) {
			return;
		}
		registerSyntheticBeanIfMissing(registry,
				"embeddedServletContainerCustomizerBeanPostProcessor",
				//导入了这个BeanPostProcessor
				//会在实例化完bean、对属性赋值之后,调用自定义的初始化方法的时候调用postProcessBeforeInitialization和postProcessAfterInitialization
				EmbeddedServletContainerCustomizerBeanPostProcessor.class);
		registerSyntheticBeanIfMissing(registry,
				"errorPageRegistrarBeanPostProcessor",
				ErrorPageRegistrarBeanPostProcessor.class);
	}
}

3.1.1. 实例化tomcat bean后调用postProcessBeforeInitialization自定义配置

  • EmbeddedServletContainerCustomizerBeanPostProcessor
public class EmbeddedServletContainerCustomizerBeanPostProcessor
		implements BeanPostProcessor, BeanFactoryAware {

	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName)
			throws BeansException {
		if (bean instanceof ConfigurableEmbeddedServletContainer) {
			//传入ConfigurableEmbeddedServletContainer
			postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);
		}
		return bean;
	}
	private void postProcessBeforeInitialization(
			ConfigurableEmbeddedServletContainer bean) {
		//获取所有定制器并且调用customize方法
		for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
			customizer.customize(bean);
		}
	}

	private Collection<EmbeddedServletContainerCustomizer> getCustomizers() {
		if (this.customizers == null) {
			// 从bean factory根据类型获取EmbeddedServletContainerCustomizer的类,并且排序
			//这是我们自定义tomcat容器配置的关键
			this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(
					this.beanFactory
							.getBeansOfType(EmbeddedServletContainerCustomizer.class,
									false, false)
							.values());
			Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);
			this.customizers = Collections.unmodifiableList(this.customizers);
		}
		return this.customizers;
	}


	//没什么用,简单返回bean
	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName)
			throws BeansException {
		return bean;
	}
}

4. 如何修改tomcat的配置

  • 配置文件
server:
  port: 8000
  • 配置类
@Bean
public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer()
{
    return new EmbeddedServletContainerCustomizer()
    {
        @Override
        public void customize(ConfigurableEmbeddedServletContainer configurableEmbeddedServletContainer)
        {
            configurableEmbeddedServletContainer.setPort(8000);
        }
    };
}

4.1. 修改之后生效的原理

  • 配置文件的方式
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties
	//配置文件对应的ServerProperties本质上是个EmbeddedServletContainer(Customizer)
	implements EmbeddedServletContainerCustomizer, EnvironmentAware, Ordered 
  • 配置类 同理

5. 总结

  • spring启动的时候refresh context有个步骤invokeBeanFactoryPostProcessors,会调用所有BeanFactoryPostProcessor的postProcessBeanDefinitionRegistry方法
  • 其中有个ConfigurationClassPostProcessor,他的postProcessBeanDefinitionRegistry方法会解析所有**@Configuration类的@Import标签,调用ImportSelector的selectImport方法以及ImportBeanDefinitionRegistrar的registerBeanDefinitions方法**
  • @Configuration EmbeddedServletContainerAutoConfiguration导入的类BeanPostProcessorsRegistrar就是其中之一,他会先容器注册EmbeddedServletContainerCustomizerBeanPostProcessor这个BeanPostProcessor
  • spring boot自动配置tomcat的EmbeddedServletContainerAutoConfiguration类会
  • spring context refresh的时候有一个步骤是onRefresh,会调用子类EmbeddedWebApplicationContext的onRefresh方法
  • 该onRefresh方法会调用@Bean TomcatEmbeddedServletContainerFactory以及EmbeddedServletContainerFactory的getEmbeddedServletContainer方法
  • getEmbeddedServletContainer方法会创建tomcat容器、配置(所有TomcatConnectorCustomizer的customize方法:包括ServerProperties)并且启动tomcat
  • spring context refresh的时候有一个步骤finishBeanFactoryInitialization,实例化Tomcat容器、为属性赋值、调用自定义方法的时候会调用EmbeddedServletContainerCustomizerBeanPostProcessor的postProcessBeforeInitialization方法,他会调用所有EmbeddedServletContainerCustomizer的customize自定义配置tomcat