前言
spring boot 一般都会加入如下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
加入后,就会启动一个嵌入式容器,其默认启动的是tomcat.那么他是如何启动的,我们接下来就分析下.
解析
- 通过之前的文章我们知道了在SpringApplication#run方法的第9步会调用AbstractApplicationContext#refresh方法,而在该方法的第5步中会调用invokeBeanFactoryPostProcessors方法,在该方法会依次调用BeanFactoryPostProcessors的postProcessBeanDefinitionRegistry 进行处理.其中ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry 会依次的扫描配置类,然后进行注册.当我们加入spring-boot-starter-web 依赖时,就会加入EmbeddedServletContainerAutoConfiguration这么一个配置类.代码如下:
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)
public class EmbeddedServletContainerAutoConfiguration {
/**
* Nested configuration if Tomcat is being used.
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
@Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory();
}
}
/**
* Nested configuration if Jetty is being used.
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Server.class, Loader.class,
WebAppContext.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedJetty {
@Bean
public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {
return new JettyEmbeddedServletContainerFactory();
}
}
/**
* Nested configuration if Undertow is being used.
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedUndertow {
@Bean
public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() {
return new UndertowEmbeddedServletContainerFactory();
}
}
/**
* Registers a {@link EmbeddedServletContainerCustomizerBeanPostProcessor}. Registered
* via {@link ImportBeanDefinitionRegistrar} for early registration.
* 在EmbeddedServletContainerAutoConfiguration自动化配置类中被导入,
* 实现了BeanFactoryAware接口(BeanFactory会被自动注入进来)和ImportBeanDefinitionRegistrar接口
* (会被ConfigurationClassBeanDefinitionReader解析并注册到Spring容器中)
*/
public static class BeanPostProcessorsRegistrar
implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
private ConfigurableListableBeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (beanFactory instanceof ConfigurableListableBeanFactory) {
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
}
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
if (this.beanFactory == null) {
return;
}
// 如果Spring容器中不存在EmbeddedServletContainerCustomizerBeanPostProcessor类型的bean
// 那么就注册一个
registerSyntheticBeanIfMissing(registry,
"embeddedServletContainerCustomizerBeanPostProcessor",
EmbeddedServletContainerCustomizerBeanPostProcessor.class);
registerSyntheticBeanIfMissing(registry,
"errorPageRegistrarBeanPostProcessor",
ErrorPageRegistrarBeanPostProcessor.class);
}
private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry,
String name, Class<?> beanClass) {
if (ObjectUtils.isEmpty(
this.beanFactory.getBeanNamesForType(beanClass, true, false))) {
RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
beanDefinition.setSynthetic(true);
registry.registerBeanDefinition(name, beanDefinition);
}
}
}
}
首先该类有@Configuration注解,因此会被ConfigurationClassPostProcessor处理,又因为有@ConditionalOnWebApplication注解.该注解如下:
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnWebApplicationCondition.class)
public @interface ConditionalOnWebApplication {
}
引入了OnWebApplicationCondition,代码如下:
class OnWebApplicationCondition extends SpringBootCondition {
private static final String WEB_CONTEXT_CLASS = "org.springframework.web.context."
+ "support.GenericWebApplicationContext";
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
// 1. 检查是否被@ConditionalOnWebApplication 注解
boolean required = metadata
.isAnnotated(ConditionalOnWebApplication.class.getName());
// 2. 判断是否是WebApplication
ConditionOutcome outcome = isWebApplication(context, metadata, required);
if (required && !outcome.isMatch()) {
// 3. 如果有@ConditionalOnWebApplication 注解,但是不是WebApplication环境,则返回不匹配
return ConditionOutcome.noMatch(outcome.getConditionMessage());
}
if (!required && outcome.isMatch()) {
// 4. 如果没有被@ConditionalOnWebApplication 注解,但是是WebApplication环境,则返回不匹配
return ConditionOutcome.noMatch(outcome.getConditionMessage());
}
// 5. 如果被@ConditionalOnWebApplication 注解,并且是WebApplication环境,则返回不匹配
return ConditionOutcome.match(outcome.getConditionMessage());
}
private ConditionOutcome isWebApplication(ConditionContext context,
AnnotatedTypeMetadata metadata, boolean required) {
ConditionMessage.Builder message = ConditionMessage.forCondition(
ConditionalOnWebApplication.class, required ? "(required)" : "");
// 1. 判断GenericWebApplicationContext是否在类路径中,如果不存在,则返回不匹配
if (!ClassUtils.isPresent(WEB_CONTEXT_CLASS, context.getClassLoader())) {
return ConditionOutcome
.noMatch(message.didNotFind("web application classes").atAll());
}
// 2. 容器里是否有名为session的scope,如果存在,则返回匹配
if (context.getBeanFactory() != null) {
String[] scopes = context.getBeanFactory().getRegisteredScopeNames();
if (ObjectUtils.containsElement(scopes, "session")) {
return ConditionOutcome.match(message.foundExactly("'session' scope"));
}
}
// 3. Environment是否为StandardServletEnvironment,如果是的话,则返回匹配
if (context.getEnvironment() instanceof StandardServletEnvironment) {
return ConditionOutcome
.match(message.foundExactly("StandardServletEnvironment"));
}
// 4. 当前ResourceLoader是否为WebApplicationContext,如果是,则返回匹配
if (context.getResourceLoader() instanceof WebApplicationContext) {
return ConditionOutcome.match(message.foundExactly("WebApplicationContext"));
}
// 5. 其他情况,返回不匹配.
return ConditionOutcome.noMatch(message.because("not a web application"));
}
}
其中getMatchOutcome是判断逻辑,做了5件事:
- 检查是否被@ConditionalOnWebApplication 注解
- 通过调用isWebApplication判断是否是Web环境,
- 判断GenericWebApplicationContext是否在类路径中,如果不存在,则返回不匹配
- 容器里是否有名为session的scope,如果存在,则返回匹配
- Environment是否为StandardServletEnvironment,如果是的话,则返回匹配
- 当前ResourceLoader是否为WebApplicationContext,如果是,则返回匹配
- 其他情况,返回不匹配.
- 如果有@ConditionalOnWebApplication 注解,但是不是WebApplication环境,则返回不匹配
- 如果没有被@ConditionalOnWebApplication 注解,但是是WebApplication环境,则返回不匹配
- 如果被@ConditionalOnWebApplication 注解,并且是WebApplication环境,则返回不匹配
那么该类是在什么时候调用的呢?调用链如下:
org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry-->
org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions-->
org.springframework.context.annotation.ConfigurationClassParser#parse-->
org.springframework.context.annotation.ConfigurationClassParser#parse-->
org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass-->
org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass-->
org.springframework.context.annotation.ConditionEvaluator#shouldSkip-->
org.springframework.boot.autoconfigure.condition.SpringBootCondition#matches-->
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition#getMatchOutcome
接下来就开始对该配置类进行解析了,通过上篇文章可以知道,会首先处理内部类,那么由于该类有4个内部类.如下:
EmbeddedTomcat
EmbeddedJetty
EmbeddedUndertow
BeanPostProcessorsRegistrar
其中BeanPostProcessorsRegistrar 没有@Configuration注解,因此该类不会被处理.
还是由之前可知,会因此判断该类是否能够被扫描,那么它们能够被扫描的条件分别为:
- EmbeddedTomcat –> 当前类路径下存在 Servlet.class, Tomcat.class,并且不含有类型为EmbeddedServletContainerFactory的bean,则该配置类生效.
- EmbeddedJetty –> 当前类路径下存在 Servlet.class, Server.class, Loader.class,WebAppContext.class ,并且不含有类型为EmbeddedServletContainerFactory的bean,则该配置类生效.
- EmbeddedUndertow–> 当前类路径下存在 Servlet.class, Undertow.class, SslClientAuthMode.class ,并且不含有类型为EmbeddedServletContainerFactory的bean,则该配置类生效.
那么当我们加入spring-boot-starter-web依赖时,默认加入了spring-boot-starter-tomcat 依赖,因此 EmbeddedTomcat 会被扫描处理.
还是由之前可知,会依次扫描EmbeddedTomcat 中被@Bean 注解的方法,因此tomcatEmbeddedServletContainerFactory方法会被处理,向BeanDefinitionRegistry注册一个id 为tomcatEmbeddedServletContainerFactory class 为 TomcatEmbeddedServletContainerFactory的bean.
还是由之前的内容可知,当EmbeddedServletContainerAutoConfiguration中的内部类处理完后,接下来会处理@Import 注解, 会依次扫描@Import所引入的类,由于此时只有一个–>BeanPostProcessorsRegistrar, 由于此类是ImportBeanDefinitionRegistrar的子类,因此为加入到ConfigurationClass的ImportBeanDefinitionRegistrar中,那么该类何时被调用呢?如下:
其registerBeanDefinitions代码如下:
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
if (this.beanFactory == null) {
return;
}
// 如果Spring容器中不存在EmbeddedServletContainerCustomizerBeanPostProcessor类型的bean
// 那么就注册一个
registerSyntheticBeanIfMissing(registry,
"embeddedServletContainerCustomizerBeanPostProcessor",
EmbeddedServletContainerCustomizerBeanPostProcessor.class);
registerSyntheticBeanIfMissing(registry,
"errorPageRegistrarBeanPostProcessor",
ErrorPageRegistrarBeanPostProcessor.class);
}
- 如果beanFactory等于null,则直接return.此处是不会发生的.因为在实例化时,就会向其注入beanFactory,因为其实现了BeanFactoryAware.
- 向容器注册id 为 embeddedServletContainerCustomizerBeanPostProcessor,类型为EmbeddedServletContainerCustomizerBeanPostProcessor的bean
- 向容器注册id 为 errorPageRegistrarBeanPostProcessor,类型为ErrorPageRegistrarBeanPostProcessor的bean.
- 还是在org.springframework.context.support.AbstractApplicationContext#refresh中的第9步,会调用onRefresh 这个扩展点,此时调用的是org.springframework.boot.context.embedded.EmbeddedWebApplicationContext#onRefresh,如下:
protected void onRefresh() {
super.onRefresh();
try {
createEmbeddedServletContainer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start embedded container",
ex);
}
}
其中调用createEmbeddedServletContainer 进行创建容器.如下:
private void createEmbeddedServletContainer() {
EmbeddedServletContainer localContainer = this.embeddedServletContainer;
// 1. 获得ServletContext
ServletContext localServletContext = getServletContext();
if (localContainer == null && localServletContext == null) { // 2 内置Servlet容器和ServletContext都还没初始化的时候执行
// 2.1 获取自动加载的工厂
EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
// 2.2 获取Servlet初始化器并创建Servlet容器,依次调用Servlet初始化器中的onStartup方法
this.embeddedServletContainer = containerFactory
.getEmbeddedServletContainer(getSelfInitializer());
}
else if (localServletContext != null) { // 3. 内置Servlet容器已经初始化但是ServletContext还没初始化,则进行初始化.一般不会到这里
try {
getSelfInitializer().onStartup(localServletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context",
ex);
}
}
// 4. 初始化PropertySources
initPropertySources();
}
4件事
- 获得ServletContext
- 如果内置Servlet容器和ServletContext都还没初始化
- 获取EmbeddedServletContainerFactory. 此时获得的是TomcatEmbeddedServletContainerFactory
- 获取Servlet初始化器并创建Servlet容器,依次调用Servlet初始化器中的onStartup方法
- 内置Servlet容器已经初始化但是ServletContext还没初始化,则进行初始化.一般不会到这里
- 初始化PropertySources,最终调用WebApplicationContextUtils#initServletPropertySources.代码如下:
public static void initServletPropertySources(
MutablePropertySources propertySources, ServletContext servletContext, ServletConfig servletConfig) {
Assert.notNull(propertySources, "'propertySources' must not be null");
if (servletContext != null && propertySources.contains(StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME) &&
propertySources.get(StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME) instanceof StubPropertySource) {
propertySources.replace(StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME,
new ServletContextPropertySource(StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME, servletContext));
}
if (servletConfig != null && propertySources.contains(StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME) &&
propertySources.get(StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME) instanceof StubPropertySource) {
propertySources.replace(StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME,
new ServletConfigPropertySource(StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME, servletConfig));
}
}
其中2.1 获得TomcatEmbeddedServletContainerFactory方法如下:
protected EmbeddedServletContainerFactory getEmbeddedServletContainerFactory() {
// Use bean names so that we don't consider the hierarchy
// 1. 从BeanFactory中获得EmbeddedServletContainerFactory 类型的容器
String[] beanNames = getBeanFactory()
.getBeanNamesForType(EmbeddedServletContainerFactory.class);
// 2. 如果没有的话,或者如果有大于1个的话,抛出异常
if (beanNames.length == 0) {
throw new ApplicationContextException(
"Unable to start EmbeddedWebApplicationContext due to missing "
+ "EmbeddedServletContainerFactory bean.");
}
if (beanNames.length > 1) {
throw new ApplicationContextException(
"Unable to start EmbeddedWebApplicationContext due to multiple "
+ "EmbeddedServletContainerFactory beans : "
+ StringUtils.arrayToCommaDelimitedString(beanNames));
}
// 3. 获得实例
return getBeanFactory().getBean(beanNames[0],
EmbeddedServletContainerFactory.class);
}
最终调用了AbstractBeanFactory#getBean,该bean触发了bean的实例化,在实例化的过程中,会触发一系列的扩展点的调用.其中,在AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization中,会调用一系列的BeanPostProcessor.在当前场景有12个,如下:
org.springframework.context.support.ApplicationContextAwareProcessor,
org.springframework.boot.context.embedded.WebApplicationContextServletContextAwareProcessor,
org.springframework.context.annotation.ConfigurationClassPostProcessor$ImportAwareBeanPostProcessor,
org.springframework.context.support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker,
org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor,
org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizerBeanPostProcessor,
org.springframework.boot.web.servlet.ErrorPageRegistrarBeanPostProcessor,
org.springframework.context.annotation.CommonAnnotationBeanPostProcessor,
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor,
org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor,
org.springframework.context.support.ApplicationListenerDetector
其中真正发挥作用的有EmbeddedServletContainerCustomizerBeanPostProcessor,ErrorPageRegistrarBeanPostProcessor.其实现分别如下:
EmbeddedServletContainerCustomizerBeanPostProcessor#postProcessBeforeInitialization
代码如下:
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
// 在Spring容器中寻找ConfigurableEmbeddedServletContainer类型的bean,SpringBoot内部的3种内置Servlet容器工厂都实现了这个接口,该接口的作用就是进行Servlet容器的配置
// 比如添加Servlet初始化器addInitializers、添加错误页addErrorPages、设置session超时时间setSessionTimeout、设置端口setPort等等
if (bean instanceof ConfigurableEmbeddedServletContainer) {
postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);
}
return bean;
}
调用
private void postProcessBeforeInitialization(
ConfigurableEmbeddedServletContainer bean) {
for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
customizer.customize(bean);
}
}
通过beanFactory 获得EmbeddedServletContainerCustomizer类型的bean,对于当前场景,有4个.如下:
org.springframework.boot.autoconfigure.websocket.TomcatWebSocketContainerCustomizer,
org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration$LocaleCharsetMappingsCustomizer,
org.springframework.boot.autoconfigure.web.ServerProperties,
org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration$DuplicateServerPropertiesDetector
TomcatWebSocketContainerCustomizer#customize
代码如下:
public void customize(ConfigurableEmbeddedServletContainer container) {
if (getContainerType().isAssignableFrom(container.getClass())) {
doCustomize((T) container);
}
}
调用
public void doCustomize(TomcatEmbeddedServletContainerFactory tomcatContainer) {
tomcatContainer.addContextCustomizers(new TomcatContextCustomizer() {
@Override
public void customize(Context context) {
addListener(context, findListenerType());
}
});
}
向tomcatContainer 添加了2个ApplicationListener:
- org.apache.tomcat.websocket.server.WsContextListener
- org.apache.catalina.deploy.ApplicationListener(tomcat7)或者org.apache.tomcat.util.descriptor.web.ApplicationListener(tomcat8)
LocaleCharsetMappingsCustomizer#customize
代码如下:
public void customize(ConfigurableEmbeddedServletContainer container) {
if (this.properties.getMapping() != null) {
container.setLocaleCharsetMappings(this.properties.getMapping());
}
}
没有执行
ServerProperties#customize
public void customize(ConfigurableEmbeddedServletContainer container) {
if (getPort() != null) {
container.setPort(getPort());
}
if (getAddress() != null) {
container.setAddress(getAddress());
}
if (getContextPath() != null) {
container.setContextPath(getContextPath());
}
if (getDisplayName() != null) {
container.setDisplayName(getDisplayName());
}
if (getSession().getTimeout() != null) {
container.setSessionTimeout(getSession().getTimeout());
}
container.setPersistSession(getSession().isPersistent());
container.setSessionStoreDir(getSession().getStoreDir());
if (getSsl() != null) {
container.setSsl(getSsl());
}
if (getJspServlet() != null) {
container.setJspServlet(getJspServlet());
}
if (getCompression() != null) {
container.setCompression(getCompression());
}
container.setServerHeader(getServerHeader());
if (container instanceof TomcatEmbeddedServletContainerFactory) {
getTomcat().customizeTomcat(this,
(TomcatEmbeddedServletContainerFactory) container);
}
if (container instanceof JettyEmbeddedServletContainerFactory) {
getJetty().customizeJetty(this,
(JettyEmbeddedServletContainerFactory) container);
}
if (container instanceof UndertowEmbeddedServletContainerFactory) {
getUndertow().customizeUndertow(this,
(UndertowEmbeddedServletContainerFactory) container);
}
// 添加SessionConfiguringInitializer这个Servlet初始化器
// SessionConfiguringInitializer初始化器的作用是基于ServerProperties的内部静态类Session设置Servlet中session和cookie的配置
container.addInitializers(new SessionConfiguringInitializer(this.session));
// 添加InitParameterConfiguringServletContextInitializer初始化器
// InitParameterConfiguringServletContextInitializer初始化器的作用是基于ServerProperties的contextParameters配置设置到ServletContext的init param中
container.addInitializers(new InitParameterConfiguringServletContextInitializer(
getContextParameters()));
}
- 设置属性
- 添加SessionConfiguringInitializer这个Servlet初始化器,SessionConfiguringInitializer初始化器的作用是基于ServerProperties的内部静态类Session设置Servlet中session和cookie的配置。
- 添加InitParameterConfiguringServletContextInitializer初始化器,InitParameterConfiguringServletContextInitializer初始化器的作用是基于ServerProperties的contextParameters配置设置到ServletContext的init param中
那么有个问题,这些属性是何时注入的呢? 在调用org.springframework.boot.context.embedded.EmbeddedWebApplicationContext#getEmbeddedServletContainerFactory,获得EmbeddedServletContainerFactory时,会调用org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization,进行扩展点的执行,其中EmbeddedServletContainerCustomizerBeanPostProcessor#postProcessBeforeInitialization执行时,会触发 加载 EmbeddedServletContainerCustomizer 类型的bean, ServerProperties实现了EmbeddedServletContainerCustomizer接口,因此会在此时被加载.
同样在加载过程中,会调用AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization.因此会触发ConfigurationPropertiesBindingPostProcessor#postProcessBeforeInitialization的调用,由于该类有@ConfigurationProperties 注解,因此会最终调用org.springframework.boot.bind.PropertiesConfigurationFactory#bindPropertiesToTarget,其后就开始真正的属性绑定,调用链如下:
org.springframework.boot.bind.PropertiesConfigurationFactory.doBindPropertiesToTarget()-->
org.springframework.validation.DataBinder.bind(PropertyValues)-->
org.springframework.validation.DataBinder.doBind(MutablePropertyValues)-->
org.springframework.boot.bind.RelaxedDataBinder.doBind(MutablePropertyValues)-->
org.springframework.validation.DataBinder.doBind(MutablePropertyValues)-->
org.springframework.validation.DataBinder.applyPropertyValues(MutablePropertyValues)-->
org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(PropertyValues, boolean, boolean)-->
org.springframework.boot.bind.RelaxedDataBinder.RelaxedBeanWrapper.setPropertyValue(PropertyValue)-->
org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(PropertyValue)-->
org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(PropertyTokenHolder, PropertyValue)-->
org.springframework.beans.AbstractNestablePropertyAccessor.processLocalProperty(PropertyTokenHolder, PropertyValue)-->
org.springframework.beans.BeanWrapperImpl.BeanPropertyHandler.setValue(Object, Object)-->
java.lang.reflect.Method.invoke(Object, Object...)-->
org.springframework.boot.autoconfigure.web.ServerProperties.setPort(Integer)
DuplicateServerPropertiesDetector#customize
代码如下:
public void customize(ConfigurableEmbeddedServletContainer container) {
// ServerProperties handles customization, this just checks we only have
// a single bean
String[] serverPropertiesBeans = this.applicationContext
.getBeanNamesForType(ServerProperties.class);
Assert.state(serverPropertiesBeans.length == 1,
"Multiple ServerProperties beans registered " + StringUtils
.arrayToCommaDelimitedString(serverPropertiesBeans));
}
只是做检查,ServerProperties类型的bean 是否为1个
ErrorPageRegistrarBeanPostProcessor#postProcessBeforeInitialization
代码如下:
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof ErrorPageRegistry) {
postProcessBeforeInitialization((ErrorPageRegistry) bean);
}
return bean;
}
调用
private void postProcessBeforeInitialization(ErrorPageRegistry registry) {
for (ErrorPageRegistrar registrar : getRegistrars()) {
registrar.registerErrorPages(registry);
}
}
在此处获得类型为ErrorPageRegistrar的bean,然后依次调用ErrorPageRegistry#registerErrorPages进行注册,默认情况下,只有ErrorPageCustomizer一个.
代码如下:
public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
ErrorPage errorPage = new ErrorPage(this.properties.getServletPrefix()
+ this.properties.getError().getPath());
errorPageRegistry.addErrorPages(errorPage);
}
path为/error
- 接下来我们分析创建容器的第2.2步. TomcatEmbeddedServletContainerFactory#getEmbeddedServletContainer代码如下:
public EmbeddedServletContainer getEmbeddedServletContainer(
ServletContextInitializer... initializers) {
// 1. 实例化Tomcat
Tomcat tomcat = new Tomcat();
// 2. 设置临时目录
File baseDir = (this.baseDirectory != null ? this.baseDirectory
: createTempDir("tomcat"));
tomcat.setBaseDir(baseDir.getAbsolutePath());
// 3. 添加Connector
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
// 4. 个性化设置
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);
return getTomcatEmbeddedServletContainer(tomcat);
}
8件事:
- 实例化Tomcat
- 设置临时目录
- 添加Connector,protocol 为 org.apache.coyote.http11.Http11NioProtocol
- 个性化设置.代码如下:
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);
}
for (TomcatConnectorCustomizer customizer : this.tomcatConnectorCustomizers) {
customizer.customize(connector);
}
}
- 设置端口号 默认是 8080
- 设置server
- 此时ProtocolHandler 为Http11NioProtocol,是AbstractProtocol 的子类,因此会执行该步骤,设置地址.
- 设置编码,默认是 UTF-8
- 如果配置了ssl,则进行ssl的设置,默认情况下不会执行
- 如果配置了压缩,则进行压缩的配置,默认不会执行
- 遍历tomcatConnectorCustomizers,进行个性化配置,默认是不存在的
- 配置引擎
- 添加Connector,一般情况下是没有的
- 准备上下文
- 实例化TomcatEmbeddedServletContainer
- 接着在org.springframework.context.support.AbstractApplicationContext#refresh中的第12步.会执行如下代码:
protected void finishRefresh() {
super.finishRefresh();
EmbeddedServletContainer localContainer = startEmbeddedServletContainer();
if (localContainer != null) {
// 发布EmbeddedServletContainerInitializedEvent事件
publishEvent(
new EmbeddedServletContainerInitializedEvent(this, localContainer));
}
}
3件事:
- 调用AbstractApplicationContext#finishRefresh
- 启动容器
- 发布EmbeddedServletContainerInitializedEvent事件
其中第2步,代码如下:
private EmbeddedServletContainer startEmbeddedServletContainer() {
EmbeddedServletContainer localContainer = this.embeddedServletContainer;
if (localContainer != null) {
localContainer.start();
}
return localContainer;
}
最终执行TomcatEmbeddedServletContainer#start,代码如下:
public void start() throws EmbeddedServletContainerException {
synchronized (this.monitor) {
if (this.started) {
return;
}
try {
addPreviouslyRemovedConnectors();
Connector connector = this.tomcat.getConnector();
if (connector != null && this.autoStart) {
startConnector(connector);
}
checkThatConnectorsHaveStarted();
this.started = true;
TomcatEmbeddedServletContainer.logger
.info("Tomcat started on port(s): " + getPortsDescription(true));
}
catch (ConnectorStartFailedException ex) {
stopSilently();
throw ex;
}
catch (Exception ex) {
throw new EmbeddedServletContainerException(
"Unable to start embedded Tomcat servlet container", ex);
}
finally {
Context context = findContext();
ContextBindings.unbindClassLoader(context, getNamingToken(context),
getClass().getClassLoader());
}
}
}
启动后,会打印如下日志:
Tomcat started on port(s):8080 (http)
第3步,对EmbeddedServletContainerInitializedEvent 感兴趣的Listener 有如下2个:
org.springframework.boot.context.config.DelegatingApplicationListener,
org.springframework.boot.context.embedded.ServerPortInfoApplicationContextInitializer
其中DelegatingApplicationListener 没有做任何事.
ServerPortInfoApplicationContextInitializer#onApplicationEvent.代码如下:
protected void onApplicationEvent(EmbeddedServletContainerInitializedEvent event) {
String propertyName = getPropertyName(event.getApplicationContext());
setPortProperty(event.getApplicationContext(), propertyName,
event.getEmbeddedServletContainer().getPort());
}
- 生成属性名–>local.server.port
- 向environment 添加了一个名为server.ports,值为配置的端口号的MapPropertySource.
参考链接