1. SpringBoot中怎么啟動Tomcat?
1.1 ServletWebServerFactoryAutoConfiguration
配置Servlet web容器。
@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 {
}
ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class 導入 WebServerFactoryCustomizerBeanPostProcessor組件,web服務工廠定制器的后置增強
導入了三種不同的web服務器(Tomcat、Jetty、Undertow),默認是Tomcat
我們來看一下 EmbeddedTomcat:
@Configuration(proxyBeanMethods = false)
class ServletWebServerFactoryConfiguration {
@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;
}
}
}
tomcatServletWebServerFactory()方法中的參數都是從容器中獲取,我們可以自定義直接放到容器中。實現ServletWebServerFactory 接口,該接口的getWebServlet()方法就是創建
Tomcat容器。
1.2 DispatcherServletAutoConfiguration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";
@Configuration(proxyBeanMethods = false)
@Conditional(DefaultDispatcherServletCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
protected static class DispatcherServletConfiguration {
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.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 incorrectly
return 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) {
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;
}
}
}
-
dispatcherServlet
方法就是往容器中注入DispatcherServlet
組件。 - dispatcherServletRegistration() 方法就是將將
DispatcherServlet
組件添加到Tomcat中(tomcat.addServlet()
)
我們來看一下DispatcherServletRegistrationBean
繼承圖:
在Tomcat啟動的時候 會調用 ServletContextInitializer.onStartup() 方法回調,將DispatcherServlet 添加到 Tomcat中,后面的啟動流程就跟SpringMVC的啟動流程是一樣的
2. 核心方法run()
下面是一個簡單的項目,我們直接開始看run方法
@SpringBootApplication
public class SpringbootDemoApplication {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(SpringbootDemoApplication.class, args);
Object demoService = run.getBean("demoService");
System.out.println("demoService = " + demoService);
}
}
我們這里直接看 SpringApplication#run(java.lang.Class<?>[], java.lang.String[]) 方法:
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
我們這里先看一下 SpringApplication 的構造函數流程:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// 保存啟動類信息
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 初始化環境。環境分為三種 非web環境、web環境、reactive環境三種。其判斷邏輯就是判斷是否存在指定的類,默認是Servlet 環境,我們這也是Servlet
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// getSpringFactoriesInstances 方法加載了 spring.factories文件。在這里進行了首次加載spring.factoies文件。設置 ApplicationContextInitializer
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 獲取監聽器,也加載了spring.factories文件
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 設置啟動類信息
this.mainApplicationClass = deduceMainApplicationClass();
}
我們下面直接來看 SpringApplication#run(java.lang.String...) 方法的執行流程:
public ConfigurableApplicationContext run(String... args) {
// 開啟關于啟動時間的信息監控
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 準備 ApplicationContext
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//java.awt.headless是J2SE的一種模式用于在缺少顯示屏、鍵盤或者鼠標時的系統配置,很多監控工具如jconsole 需要將該值設置為true,系統變量默認為true
configureHeadlessProperty();
// 1. 獲取Spring的監聽器類,這里是從 spring.factories 中去獲取,默認的是以 org.springframework.boot.SpringApplicationRunListener 為key,獲取到的監聽器類型為 EventPublishingRunListener。
SpringApplicationRunListeners listeners = getRunListeners(args);
// 1.1 監聽器發送啟動事件
listeners.starting();
try {
// 封裝參數
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 2. 構造容器環境。將容器的一些配置內容加載到 environment 中
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// 配置BeanInfo的忽略 :“spring.beaninfo.ignore”,值為“true”表示跳過對BeanInfo類的搜索
configureIgnoreBeanInfo(environment);
// 打印信息對象
Banner printedBanner = printBanner(environment);
// 3. 創建上下文對象
context = createApplicationContext();
// 從 spring.factries 中獲取錯誤報告的類。出錯的時候會調用其方法通知
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 4. 準備刷新上下文
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 5. 刷新上下文
refreshContext(context);
// 結束刷新,留待擴展功能,并未實現什么
afterRefresh(context, applicationArguments);
// 停止監聽
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// 監聽器發送啟動結束時間
listeners.started(context);
// 調用 ApplicationRunner 和 CommandLineRunner 對應的方法
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// 發送容器運行事件
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
3. run()流程步驟詳解
下面我們重點分析幾個步驟
3.1 獲取監聽器
這一步是從 spring.factories 文件中獲取監聽器集合,當有事件發生時調用監聽器對應事件的方法。
默認的是以 org.springframework.boot.SpringApplicationRunListener 為key,獲取到的監聽器類型為 EventPublishingRunListener。
SpringApplicationRunListeners listeners = getRunListeners(args);
其詳細代碼如下:
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}
這里需要注意的是 getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args) 返回的是一個Collection 類型。也就是說明在 SpringApplicationRunListeners并非代表一個監聽器,而是保存了監聽器集合,在默認情況下,僅有一個 EventPublishingRunListener。在 SpringApplicationRunListeners 類中也能看到,如下:
class SpringApplicationRunListeners {
private final Log log;
private final List<SpringApplicationRunListener> listeners;
SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) {
this.log = log;
this.listeners = new ArrayList<>(listeners);
}
...
}
總結一下: Spring啟動時,通過 spring.factories
文件中獲取監聽器集合。默認類型為 EventPublishingRunListener
。在事件發生時,EventPublishingRunListener
會尋找容器中 ApplicationListener
的bean,并進行事件通知。詳見Spring5源碼12-監聽器原理
3.2 環境變量的構造
這一步的作用就是加載一些配置文件的內容
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
其具體實現如下:
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
// 獲取或者創建 environment。這里獲取類型是 StandardServletEnvironment
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 將入參配置到環境配置中
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
// 發布環境準備事件。
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
private Class<? extends StandardEnvironment> deduceEnvironmentClass() {
switch (this.webApplicationType) {
case SERVLET:
return StandardServletEnvironment.class;
case REACTIVE:
return StandardReactiveWebEnvironment.class;
default:
return StandardEnvironment.class;
}
}
關于 webApplicationType 的值,在 org.springframework.boot.SpringApplication#SpringApplication(org.springframework.core.io.ResourceLoader, java.lang.Class<?>...) 構造函數中進行了賦值為Servlet。所以我們這里可以知道 Environment 類型為 StandardServletEnvironment 。
在 listeners.environmentPrepared(environment); 時會發送環境準備事件,環境準備事件要通知監聽器如下。對于 Springboot 的配置文件application.yml或者application.properties文件的加載實際上是通過發布環境準備事件完成的,完成這項功能的就是ConfigDataEnvironmentPostProcessor。
3.2.1 application.yml 的加載
SpringBoot在啟動時,讀取所有的ApplicationListener下的配置類。spring-boot工程下的spring.factories文件:
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.env.EnvironmentPostProcessorApplicationListener
當EnvironmentPostProcessorApplicationListener類接收到ApplicationEnvironmentPreparedEvent的事件時,就會調用EnvironmentPostProcessorsFactory類下的getEnvironmentPostProcessors方法來獲取所有的EnvironmentPostProcessor下的配置類,并觸發這些類的postProcessEnvironment方法。
public class EnvironmentPostProcessorApplicationListener implements SmartApplicationListener, Ordered {
...
public EnvironmentPostProcessorApplicationListener() {
this(EnvironmentPostProcessorsFactory::fromSpringFactories, new DeferredLogs());
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
}
...
}
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
...
for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(application.getResourceLoader(),
event.getBootstrapContext())) {
postProcessor.postProcessEnvironment(environment, application);
}
}
List<EnvironmentPostProcessor> getEnvironmentPostProcessors(ResourceLoader resourceLoader,
ConfigurableBootstrapContext bootstrapContext) {
...
EnvironmentPostProcessorsFactory postProcessorsFactory = this.postProcessorsFactory.apply(classLoader);
return postProcessorsFactory.getEnvironmentPostProcessors(this.deferredLogs, bootstrapContext);
}
}
@FunctionalInterface
public interface EnvironmentPostProcessorsFactory {
List<EnvironmentPostProcessor> getEnvironmentPostProcessors(DeferredLogFactory logFactory,
ConfigurableBootstrapContext bootstrapContext);
static EnvironmentPostProcessorsFactory fromSpringFactories(ClassLoader classLoader) {
return new ReflectionEnvironmentPostProcessorsFactory(classLoader,
SpringFactoriesLoader.loadFactoryNames(EnvironmentPostProcessor.class, classLoader));
}
spring.factories文件中 :
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor,\
org.springframework.boot.env.RandomValuePropertySourceEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor,\
org.springframework.boot.reactor.DebugAgentEnvironmentPostProcessor
EnvironmentPostProcessor
EnvironmentPostProcessor是處理程序的配置的,不同的子類實現不同的配置,有處理json配置文件的、有處理yaml配置文件的、有替換配置文件中的環境變量的。
SpringBoot 2.4.0之前的版本,處理yaml配置文件的類是ConfigFileApplicationListener類
SpringBoot 2.4.0之后的版本,處理yaml配置文件的類是ConfigDataEnvironmentPostProcessor類,我們重點看這個。
ConfigDataEnvironmentPostProcessor
SpringBoot 2.4.0之后的版本,使用ConfigDataEnvironmentPostProcessor 類來加載yaml文件中的配置。
public class ConfigDataEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
postProcessEnvironment(environment, application.getResourceLoader(), application.getAdditionalProfiles());
}
void postProcessEnvironment(ConfigurableEnvironment environment, ResourceLoader resourceLoader,
Collection<String> additionalProfiles) {
...
getConfigDataEnvironment(environment, resourceLoader, additionalProfiles).processAndApply();
...
}
ConfigDataEnvironment getConfigDataEnvironment(ConfigurableEnvironment environment, ResourceLoader resourceLoader, Collection<String> additionalProfiles) {
return new ConfigDataEnvironment(this.logFactory, this.bootstrapContext, environment, resourceLoader, additionalProfiles, this.environmentUpdateListener);
}
}
ConfigDataEnvironment
ConfigDataEnvironmentPostProcessor 在postProcessEnvironment方法中會實例化一個ConfigDataEnvironment對象,這個對象會加載optional:classpath:/;optional:classpath:/config/;optional:file:./;optional:file:./config/;optional:file:./config/*/路徑下的yaml文件。
class ConfigDataEnvironment {
static final String LOCATION_PROPERTY = "spring.config.location";
static final String ADDITIONAL_LOCATION_PROPERTY = "spring.config.additional-location";
static final String IMPORT_PROPERTY = "spring.config.import";
static final ConfigDataLocation[] DEFAULT_SEARCH_LOCATIONS;
static {
List<ConfigDataLocation> locations = new ArrayList<>();
locations.add(ConfigDataLocation.of("optional:classpath:/;optional:classpath:/config/"));
locations.add(ConfigDataLocation.of("optional:file:./;optional:file:./config/;optional:file:./config/*/"));
DEFAULT_SEARCH_LOCATIONS = locations.toArray(new ConfigDataLocation[0]);
}
private ConfigDataEnvironmentContributors createContributors(Binder binder) {
...
contributors.addAll(getInitialImportContributors(binder));
...
return createContributors(contributors);
}
protected ConfigDataEnvironmentContributors createContributors(
List<ConfigDataEnvironmentContributor> contributors) {
return new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapContext, contributors);
}
private List<ConfigDataEnvironmentContributor> getInitialImportContributors(Binder binder) {
List<ConfigDataEnvironmentContributor> initialContributors = new ArrayList<>();
addInitialImportContributors(initialContributors, bindLocations(binder, IMPORT_PROPERTY, EMPTY_LOCATIONS));
addInitialImportContributors(initialContributors,
bindLocations(binder, ADDITIONAL_LOCATION_PROPERTY, EMPTY_LOCATIONS));
addInitialImportContributors(initialContributors,
bindLocations(binder, LOCATION_PROPERTY, DEFAULT_SEARCH_LOCATIONS));
return initialContributors;
}
}
spring.factories文件中 :
# ConfigData Location Resolvers
org.springframework.boot.context.config.ConfigDataLocationResolver=\
org.springframework.boot.context.config.ConfigTreeConfigDataLocationResolver,\
org.springframework.boot.context.config.StandardConfigDataLocationResolver
# ConfigData Loaders
org.springframework.boot.context.config.ConfigDataLoader=\
org.springframework.boot.context.config.ConfigTreeConfigDataLoader,\
org.springframework.boot.context.config.StandardConfigDataLoader
# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
processAndApply()
請求路徑為:processAndApply() -> ConfigDataEnvironment#processAndApply -> ConfigDataEnvironment#processWithoutProfiles -> ConfigDataEnvironmentContributors#withProcessedImports -> ConfigDataImporter#resolveAndLoad -> ConfigDataImporter#load -> ConfigDataLoaders#load -> StandardConfigDataLoader#load -> YamlPropertySourceLoader#load
我們直接看YamlPropertySourceLoader#load 方法:
在這里加載 application.yml文件。
EnvironmentPostProcessor
如果想處理加載的配置文件,可以在自己的java項目里添加spring.factories,然后實現EnvironmentPostProcessor類。
org.springframework.boot.env.EnvironmentPostProcessor=\
com.camellibby.springboot.component.DemoEnvironmentPostProcessor
public class DemoEnvironmentPostProcessor implements EnvironmentPostProcessor {
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
System.out.println("DemoEnvironmentPostProcessor is starting");
}
}
3.3 創建上下文
這一步是創建上下文了:
context = createApplicationContext();
其詳細內容如下:
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
+ "annotation.AnnotationConfigApplicationContext";
public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot."
+ "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."
+ "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";
protected ConfigurableApplicationContext createApplicationContext() {
return this.applicationContextFactory.create(this.webApplicationType);
}
// org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext.Factory#create
static class Factory implements ApplicationContextFactory {
@Override
public ConfigurableApplicationContext create(WebApplicationType webApplicationType) {
return (webApplicationType != WebApplicationType.SERVLET) ? null
: new AnnotationConfigServletWebServerApplicationContext();
}
}
public AnnotationConfigServletWebServerApplicationContext() {
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
很明顯,因為我們知道 webApplicationType 值是 servlet,所以這里創建的是 AnnotationConfigServletWebServerApplicationContext 類型的上下文
這里需要注意:AnnotationConfigServletWebServerApplicationContext 構造函數中會創建 AnnotatedBeanDefinitionReader。而在 AnnotatedBeanDefinitionReader 構造函數中會調用 AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);,該方法將一些必要Bean(如ConfigurationClassPostProcessor、AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor 等)注入到了容器中。
3.4 上下文準備工作
上面一步,僅僅是將上下文創建出來了,并沒有對上下文進行操作。這一步開始對上下文的準備操作。
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
其詳細內容如下:
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
// 設置上下文的環境變量
context.setEnvironment(environment);
// 執行容器后置處理 : 可以注冊beanName策略生成器、設置資源加載器,設置轉換服務等。但這里默認是沒有做任何處理。目的是留給后續可以擴展
postProcessApplicationContext(context);
// 處理所有的初始化類的初始化方法。即 spring.factories 中key 為 org.springframework.context.ApplicationContextInitializer 指向的類,調用其 initialize 方法
applyInitializers(context);
// 向監聽器發送容器準備事件
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
// 獲取上下文中的 BeanFactory。這里的BeanFactory 實際類型是 DefaultListableBeanFactory。BeanFactory 在初始化的時候,直接在構造函數里創建為 DefaultListableBeanFactory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 注冊 springApplicationArguments等一系列bean
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
// 設置是否允許bean定義覆蓋
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
// 如果允許懶加載,則添加對應的BeanFactory后置處理器
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// Load the sources
// 這里加載的實際上是啟動類
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 這里將啟動類加入到 beanDefinitionMap 中,為后續的自動化配置做好了基礎
load(context, sources.toArray(new Object[0]));
// 發送容器加載完成事件
listeners.contextLoaded(context);
}
....
// 需要注意這里的 sources參數實際上是 啟動類的 Class
protected void load(ApplicationContext context, Object[] sources) {
if (logger.isDebugEnabled()) {
logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
}
// 從上下文中獲取 BeanDefinitionRegistry并依次創建出 BeanDefinitionLoader 。這里將sources作為參數保存到了 loader 中。也就是 loader 中保存了 啟動類的Class信息
BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
if (this.beanNameGenerator != null) {
loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader != null) {
loader.setResourceLoader(this.resourceLoader);
}
if (this.environment != null) {
loader.setEnvironment(this.environment);
}
loader.load();
}
我們這里比較關鍵的方法是 loader.load(); 方法 其中 loader.load(); 不管怎么跳轉,最后都會跳轉到 BeanDefinitionLoader#load(java.lang.Class<?>) 方法中。如下:
private int load(Class<?> source) {
// 判斷是否存在 groovy 加載方式
if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
// Any GroovyLoaders added in beans{} DSL can contribute beans here
GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);
load(loader);
}
// 判斷 source 是否 需要被加載到Spring容器中。實際上是根據判斷是否存在 @Component
if (isComponent(source)) {
// 將source 就是啟動類的 class,注冊到 annotatedReader 中。annotatedReader 類型是AnnotatedBeanDefinitionReader。
this.annotatedReader.register(source);
return 1;
}
return 0;
}
this.annotatedReader.register(source); 后續會跳轉到 AnnotatedBeanDefinitionReader#doRegisterBean 方法中,看名字就知道是這個方法的工作是 注冊 Bean。實際上,在這個方法中完成了對@Qualifier 以及一些其他注解的處理。具體如下:
// 這里的 beanClass 其實就是啟動類的 beanClass
private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,
@Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,
@Nullable BeanDefinitionCustomizer[] customizers) {
// 將Class 轉換成一個 BeanDefinition 類
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
// 判斷是否應該跳過
if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
return;
}
abd.setInstanceSupplier(supplier);
// 保存其作用域信息。這里默認是 singleton
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
abd.setScope(scopeMetadata.getScopeName());
// 獲取 beanName
String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
// 處理一些通用的注解信息,包括Lazy、Primary、DependsOn、Role、Description 注解。獲取其value值并保存到 abd 中
AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
// 處理 @Qualifier
if (qualifiers != null) {
for (Class<? extends Annotation> qualifier : qualifiers) {
if (Primary.class == qualifier) {
abd.setPrimary(true);
}
else if (Lazy.class == qualifier) {
abd.setLazyInit(true);
}
else {
abd.addQualifier(new AutowireCandidateQualifier(qualifier));
}
}
}
if (customizers != null) {
for (BeanDefinitionCustomizer customizer : customizers) {
customizer.customize(abd);
}
}
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
// 判斷是否需要創建代理,需要則創建
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
// 將 BeanDefinitionHolder 注冊到 容器中。此時的 registry 就是 AnnotationConfigServletWebServerApplicationContext。在BeanDefinitionLoader 初始化的時候保存的
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}
3.5 SpringApplication#refreshContext
對容器進行一個刷新工作。在此進行了大量的工作。這里的處理工作就由Springboot交給 Spring來處理了
refreshContext(context);
詳細如下:
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
最終會跳轉到 AbstractApplicationContext#refresh
中。而關于 AbstractApplicationContext#refresh
方法在之前的文章中有過介紹,參見Spring5源碼11-容器刷新refresh方法(注解版)。我們重點看一下Tomcat的啟動過程,著重看onRefresh()
方法:
@Override
public void refresh() throws BeansException, IllegalStateException {
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
}
SpringBoot使用ServletWebServerApplicationContext#onRefresh:
@Override
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
直接看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());
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();
}
3.5.1 getWebServerFactory()
如下所示,從容器中獲取ServletWebServerFactory類型的bean,唯一一個,否則拋出異常。本文環境獲取的是tomcatServletWebServerFactory。
protected ServletWebServerFactory getWebServerFactory() {
// Use bean names so that we don't consider the hierarchy
String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
if (beanNames.length == 0) {
throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing "
+ "ServletWebServerFactory bean.");
}
if (beanNames.length > 1) {
throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "
+ "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
}
return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}
3.5.2 getSelfInitializer()
ServletWebServerApplicationContext的getSelfInitializer方法,返回的是ServletContextInitializer。
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
return this::selfInitialize;
}
看到this::selfInitialize是不是比較迷糊?典型的java8的lambda寫法。我們看一下ServletContextInitializer 可能就明白了。
如下所示,其是一個函數式接口,只有一個onStartup方法。函數式接口(有且僅有一個抽象方法的接口)可以使用lambda式的寫法。
@FunctionalInterface
public interface ServletContextInitializer {
// 初始化過程中,使用給定的servlets、filters、listeners
//context-params and attributes necessary配置ServletContext
void onStartup(ServletContext servletContext) throws ServletException;
}
我們這里獲取到的本質是一個lambda
,參數則是當前this
,如下圖所示:
this::selfInitialize
中的selfInitialize
則指的是ServletWebServerApplicationContext的selfInitialize
方法。this指的是AnnotationConfigServletWebServerApplicationContext
,其繼承于ServletWebServerApplicationContext
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
其實換成匿名類的寫法則是:
new ServletContextInitializer() {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
selfInitialize(servletContext);
}
};
3.5.3 getWebServer()
本文這里是TomcatServletWebServerFactory#getWebServer方法。
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
// registry = new NoDescriptorRegistry();
Registry.disableRegistry();
}
//實例化Tomcat
Tomcat tomcat = new Tomcat();
//獲取臨時路徑
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
//設置基礎路徑
tomcat.setBaseDir(baseDir.getAbsolutePath());
//實例化Connector 并進行配置
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
//這里會實例化server service
tomcat.getService().addConnector(connector);
customizeConnector(connector);
//對Connector做配置比如Protocol、URIEncoding
tomcat.setConnector(connector);
//這里會實例化Engine、Host
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
// todo
tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
return getTomcatWebServer(tomcat);
}
getService
getService首先會觸發getServer然后獲取service。getServer如下所示會實例化Server并對其進行配置。
public Service getService() {
return getServer().findServices()[0];
}
public Server getServer() {
if (server != null) {
return server;
}
System.setProperty("catalina.useNaming", "false");
// 實例化 server
server = new StandardServer();
// 對basedir做處理
initBaseDir();
// Set configuration source
ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(new File(basedir), null));
// 為server設置port和service
server.setPort( -1 );
//實例化service
Service service = new StandardService();
service.setName("Tomcat");
server.addService(service);
return server;
}
prepareContext
這里會實例化TomcatEmbeddedContext并對其進行配置。
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
File documentRoot = getValidDocumentRoot();
TomcatEmbeddedContext context = new TomcatEmbeddedContext();
if (documentRoot != null) {
context.setResources(new LoaderHidingResourceRoot(context));
}
context.setName(getContextPath());
context.setDisplayName(getDisplayName());
context.setPath(getContextPath());
File docBase = (documentRoot != null) ? documentRoot : createTempDir("tomcat-docbase");
context.setDocBase(docBase.getAbsolutePath());
context.addLifecycleListener(new FixContextListener());
context.setParentClassLoader((this.resourceLoader != null) ? this.resourceLoader.getClassLoader()
: ClassUtils.getDefaultClassLoader());
resetDefaultLocaleMapping(context);
addLocaleMappings(context);
context.setUseRelativeRedirects(false);
try {
context.setCreateUploadTargets(true);
}
catch (NoSuchMethodError ex) {
// Tomcat is < 8.5.39. Continue.
}
configureTldSkipPatterns(context);
WebappLoader loader = new WebappLoader(context.getParentClassLoader());
loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName());
loader.setDelegate(true);
context.setLoader(loader);
if (isRegisterDefaultServlet()) {
addDefaultServlet(context);
}
if (shouldRegisterJspServlet()) {
addJspServlet(context);
addJasperInitializer(context);
}
context.addLifecycleListener(new StaticResourceConfigurer(context));
ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
host.addChild(context);
configureContext(context, initializersToUse);
postProcessContext(context);
}
getTomcatWebServer
這個方法很簡單,只是直接實例化了TomcatWebServer返回。其構造方法觸發了initialize,這會引起后續一系列動作,包括tomcat.start()。
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return 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;
// todo
initialize();
}
initialize()
我們回過頭來,看一下initialize()方法:
private void initialize() throws WebServerException {
logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
...
// Start the server to trigger initialization listeners
this.tomcat.start();
...
}
}
}
在這里將嵌入式Tomcat啟動。
selfInitialize
獲取到TomcatWebServer后,Tomcat啟動之后就觸發了selfInitialize方法。這里servletContext其實是獲取了ApplicationContext的一個門面/外觀–ApplicationContextCade。
// ServletWebServerApplicationContext
private void selfInitialize(ServletContext servletContext) throws ServletException {
// todo
prepareWebApplicationContext(servletContext);
// todo
registerApplicationScope(servletContext);
// todo
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
// todo
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
prepareWebApplicationContext
ServletWebServerApplicationContext的prepareWebApplicationContext方法如下所示,簡單來講就是為servletContext設置根容器屬性并為當前應用上下文ApplicationContext設置servletContext引用。
protected void prepareWebApplicationContext(ServletContext servletContext) {
//嘗試從servletContext中獲取rootContext
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!");
}
return;
}
Log logger = LogFactory.getLog(ContextLoader.class);
// 這個日志是不是很熟悉?!
servletContext.log("Initializing Spring embedded WebApplicationContext");
try {
//向servletContext設置屬性 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this);
if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name ["
+ WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
// 為ApplicationContext設置servletContext引用
setServletContext(servletContext);
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - getStartupDate();
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
}
catch (RuntimeException | Error ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
}
registerApplicationScope:
ServletWebServerApplicationContext的registerApplicationScope方法如下所示,簡單來講就是(擴展)注冊scope-application。這里會實例化一個ServletContextScope (包裝了servletContext),然后注冊到BeanFactory中并為servletContext設置屬性。
private void registerApplicationScope(ServletContext servletContext) {
ServletContextScope appScope = new ServletContextScope(servletContext);
// application
getBeanFactory().registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope);
// Register as ServletContext attribute, for ContextCleanupListener to detect it.
servletContext.setAttribute(ServletContextScope.class.getName(), appScope);
}
我們在Spring中refresh分析之postProcessBeanFactory方法詳解提到了request-RequestScope,session–SessionScope的注冊,本文這里注冊了application-ServletContextScope注冊。
registerEnvironmentBeans:
WebApplicationContextUtils的registerEnvironmentBeans方法。
public static void registerEnvironmentBeans(ConfigurableListableBeanFactory bf, @Nullable ServletContext sc) {
registerEnvironmentBeans(bf, sc, null);
}
public static void registerEnvironmentBeans(ConfigurableListableBeanFactory bf,
@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {
//將servletContext作為單例注冊容器
if (servletContext != null && !bf.containsBean(WebApplicationContext.SERVLET_CONTEXT_BEAN_NAME)) {
bf.registerSingleton(WebApplicationContext.SERVLET_CONTEXT_BEAN_NAME, servletContext);
}
// 將servletConfig 作為單例注冊容器本文這里沒有觸發
if (servletConfig != null && !bf.containsBean(ConfigurableWebApplicationContext.SERVLET_CONFIG_BEAN_NAME)) {
bf.registerSingleton(ConfigurableWebApplicationContext.SERVLET_CONFIG_BEAN_NAME, servletConfig);
}
// String CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters";
if (!bf.containsBean(WebApplicationContext.CONTEXT_PARAMETERS_BEAN_NAME)) {
Map<String, String> parameterMap = new HashMap<>();
if (servletContext != null) {
// 獲取servletContextd的初始化參數
Enumeration<?> paramNameEnum = servletContext.getInitParameterNames();
while (paramNameEnum.hasMoreElements()) {
String paramName = (String) paramNameEnum.nextElement();
parameterMap.put(paramName, servletContext.getInitParameter(paramName));
}
}
// 本文這里servletConfig 為null
if (servletConfig != null) {
// // 獲取servletConfig的初始化參數
Enumeration<?> paramNameEnum = servletConfig.getInitParameterNames();
while (paramNameEnum.hasMoreElements()) {
String paramName = (String) paramNameEnum.nextElement();
parameterMap.put(paramName, servletConfig.getInitParameter(paramName));
}
}
// 將contextParameters作為單例注冊到容器
bf.registerSingleton(WebApplicationContext.CONTEXT_PARAMETERS_BEAN_NAME,
Collections.unmodifiableMap(parameterMap));
}
// String CONTEXT_ATTRIBUTES_BEAN_NAME = "contextAttributes";
if (!bf.containsBean(WebApplicationContext.CONTEXT_ATTRIBUTES_BEAN_NAME)) {
Map<String, Object> attributeMap = new HashMap<>();
if (servletContext != null) {
Enumeration<?> attrNameEnum = servletContext.getAttributeNames();
while (attrNameEnum.hasMoreElements()) {
String attrName = (String) attrNameEnum.nextElement();
attributeMap.put(attrName, servletContext.getAttribute(attrName));
}
}
// 將contextAttributes作為單例注冊到容器
bf.registerSingleton(WebApplicationContext.CONTEXT_ATTRIBUTES_BEAN_NAME,
Collections.unmodifiableMap(attributeMap));
}
}
觸發ServletContextInitializer的onStartup:如下所示,這里會獲取ServletContextInitializer的所有實例,遍歷觸發其onStartup方法。
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
如下所示,將會挨個觸發這5個的onStartup方法:
核心代碼如下:
// org.springframework.boot.web.servlet.RegistrationBean#onStartup
@Override
public final void onStartup(ServletContext servletContext) throws ServletException {
String description = getDescription();
if (!isEnabled()) {
logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
return;
}
register(description, servletContext);
}
// org.springframework.boot.web.servlet.DynamicRegistrationBean#register
@Override
protected final void register(String description, ServletContext servletContext) {
D registration = addRegistration(description, servletContext);
if (registration == null) {
logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)");
return;
}
configure(registration);
}
// org.springframework.boot.web.servlet.ServletRegistrationBean#addRegistration
@Override
protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
String name = getServletName();
return servletContext.addServlet(name, this.servlet);
}
在這里,會將Dispatcher加入到Tomcat中,之后就是Tomcat的啟動流程,跟之前啟動SpringMVC流程一樣
3.6 afterRefresh
進入org.springframework.boot.SpringApplication#afterRefresh方法中
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
}
這里啥也沒做,就是一個模板方法,可能使用于擴展性的吧
3.7 callRunners
進入org.springframework.boot.SpringApplication#callRunners方法中
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
try {
(runner).run(args);
}
catch (Exception ex) {
throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
}
}
可以看到這里主要就是從容器中獲取runner對象并調用對應的run方法。