NOTE
2.40 原理分析
1. 开启ServletContainer自动配置 - @EmbeddedServletContainerAutoConfiguration EmbeddedServletContainerAutoConfiguration是spring boot自动配置嵌入式的servlet容器的自动配置类 他会
这是历史学习笔记,可能存在过时或不完整的理解。
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