上文回顧
- 理解自動裝配的核心原理
- 能手寫一個EnableAutoConfiguration注解
- 理解SPI機制的原理
學習目標
- 理解springboot的總體啟動流程,并能口述大概
- 理清配置文件的加載流程
第1章 mAIn入口
public static void main(String[] args) {
//代碼很簡單SpringApplication.run();
SpringApplication.run(ConsumerApp.class, args);
}
public static ConfigurableApplicationContext run(Class<?> primarySource,
String... args) {
//這個里面調(diào)用了run() 方法,我們轉(zhuǎn)到定義
return run(new Class<?>[] { primarySource }, args);
}
//這個run方法代碼也很簡單,就做了兩件事情
//1、new了一個SpringApplication() 這么一個對象
//2、執(zhí)行new出來的SpringApplication()對象的run()方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
上面代碼主要做了兩件事情。
- 第一步new了一個SpringApplication對象
- 第二步調(diào)用了run()方法。
1.1 SpringApplication
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
//1、先把主類保存起來
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//2、判斷運行項目的類型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//3、掃描當前路徑下META-INF/spring.factories文件的,加載ApplicationContextInitializer接口實例
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//4、掃描當前路徑下META-INF/spring.factories文件的,加載ApplicationListener接口實例
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
1.1.1 判斷運行環(huán)境
構(gòu)造方法內(nèi)會調(diào)用枚舉WebApplicationType的deduceFromClasspath方法獲得應(yīng)用類型并設(shè)置當前應(yīng)用是普通web應(yīng)用、響應(yīng)式web應(yīng)用還是非web應(yīng)用。
this.webApplicationType = WebApplicationType.deduceFromClasspath();
deduceFromClasspath方法由枚舉WebApplicationType提供,具體實現(xiàn)如下:
static WebApplicationType deduceFromClasspath() {
//當classpath下只存在org.springframework.web.reactive.DispatcherHandler,
//且不存在org.springframework.web.servlet.DispatcherServlet,也不存在
//org.glassfish.jersey.servlet.ServletContainer則運行環(huán)境為reactive,該模式是非阻塞模式
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
推斷的過程中重點調(diào)用了ClassUtils.isPresent()方法,用來判斷指定類名的類是否存在,是否可以進行加載。ClassUtils.isPresent()方法源代碼如下:
public static boolean isPresent(String className, @Nullable ClassLoader classLoader) {
try {
forName(className, classLoader);
return true;
}
catch (IllegalAccessError err) {
throw new IllegalStateException("Readability mismatch in inheritance hierarchy of class [" +
className + "]: " + err.getMessage(), err);
}
catch (Throwable ex) {
// Typically ClassNotFoundException or NoClassDefFoundError...
return false;
}
}
isPresent()方法調(diào)用了forName()方法,如果在調(diào)用forName()方法的過程中出現(xiàn)異常則返回false,也就是目標類不存在。否則,返回true。
看一下forName()方法的部分代碼:
public static Class<?> forName(String name, @Nullable ClassLoader classLoader)
throws ClassNotFoundException, LinkageError {
// 此處省略一些非空和基礎(chǔ)類型的判斷邏輯代碼
ClassLoader clToUse = classLoader;
if (clToUse == null) {
//如果為空則獲取默認classLoader
clToUse = getDefaultClassLoader();
}
try {
// 返回加載戶的Class。
return Class.forName(name, false, clToUse);
} catch (ClassNotFoundException ex) {
// 如果直接加載類出現(xiàn)異常,則嘗試加載內(nèi)部類。
int lastDotIndex = name.lastIndexOf(PACKAGE_SEPARATOR);
if (lastDotIndex != -1) {
// 拼接內(nèi)部類
String innerClassName =
name.substring(0, lastDotIndex) + INNER_CLASS_SEPARATOR + name.substring(lastDotIndex + 1);
try {
return Class.forName(innerClassName, false, clToUse);
}
catch (ClassNotFoundException ex2) {
// Swallow - let original exception get through
}
}
throw ex;
}
}
通過以上核心代碼,可得知forName()方法主要做的事情就是獲得類加載器,嘗試直接加載類,如果失敗則嘗試加載該類的內(nèi)部類,如果依舊失敗,則拋出異常。
因此,整個應(yīng)用類型的推斷分以下步驟:
- SpringBoot調(diào)用SpringApplication構(gòu)造方法;
- SpringApplication構(gòu)造方法調(diào)用枚舉類的類型推斷方法deduceFromClasspath()。
- deduceFromClasspath()方法通過ClassUtils.isPresent()返回結(jié)果為true或false來確定是否加載成功指定的類。
- ClassUtils.isPresent()方法通過調(diào)用forName()方法并捕獲異常來確定是否能夠成功加載該類。
- forName()方法通過嘗試加載指定類和指定類的內(nèi)部類來確定該類是否存在,存在則返回該類,不存在則拋異常。
在類型推斷的過程中枚舉類WebApplicationType定義了具體去加載哪些類:
private static final String[] SERVLET_INDICATOR_CLASSES = { "JAVAx.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework."
+ "web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS = "org."
+ "springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
- 如果應(yīng)用程序存在DispatcherHandler并且不存在DispatcherServlet和ServletContainer則為響應(yīng)式web應(yīng)用,需加載并啟動內(nèi)嵌的響應(yīng)式web服務(wù)。
- 如果應(yīng)用程序不包含Servlet和ConfigurableWebApplicationContext則為普通應(yīng)用程序。
- 其他情況則為基于servlet的web應(yīng)用,需加載并啟動內(nèi)嵌的web服務(wù)。
1.1.2 初始化器和監(jiān)聽器
利用SPI機制掃描 META-INF/spring.factories 這個文件,并且加載
ApplicationContextInitializer、ApplicationListener 接口實例。
1、
ApplicationContextInitializer 這個類當springboot上下文Context初始化完成后會調(diào)用
2、ApplicationListener 當springboot啟動時事件change后都會觸發(fā)
總結(jié):上面就是SpringApplication初始化的代碼,new SpringApplication()沒做啥事情 ,利用SPI機制主要加載了META-INF/spring.factories 下面定義的事件監(jiān)聽器接口實現(xiàn)類
1.2 執(zhí)行run方法
public ConfigurableApplicationContext run(String... args) {
<!--1、這個是一個計時器,沒什么好說的-->
StopWatch stopWatch = new StopWatch();
stopWatch.start();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
<!--2、這個也不是重點,就是設(shè)置了一些環(huán)境變量-->
configureHeadlessProperty();
<!--3、獲取事件監(jiān)聽器SpringApplicationRunListener類型,并且執(zhí)行starting()方法-->
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
<!--4、把參數(shù)args封裝成DefaultApplicationArguments,這個了解一下就知道-->
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
<!--5、這個很重要準備環(huán)境了,并且把環(huán)境跟spring上下文綁定好,并且執(zhí)行environmentPrepared()方法-->
//準備容器環(huán)境、這里會加載配置文件。在這個方法里面會調(diào)用所有監(jiān)聽器Listener的onApplicationEvent(event);
// 此時有一個與配置文件相關(guān)的監(jiān)聽器就會被加載`ConfigFileApplicationListener`
ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
<!--6、判斷一些環(huán)境的值,并設(shè)置一些環(huán)境的值-->
configureIgnoreBeanInfo(environment);
<!--7、打印banner-->
Banner printedBanner = printBanner(environment);
<!--8、創(chuàng)建上下文,根據(jù)項目類型創(chuàng)建上下文-->
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
<!--9、準備上下文,執(zhí)行完成后調(diào)用contextPrepared()方法,contextLoaded()方法-->
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
<!--10、這個是spring啟動的代碼了,這里就回去里面就回去掃描并且初始化單實列bean了-->
//這個refreshContext()加載了bean,還啟動了內(nèi)置web容器,需要細細的去看看
refreshContext(context);
<!--11、啥事情都沒有做-->
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
<!--12、執(zhí)行ApplicationRunListeners中的started()方法-->
listeners.started(context);
<!--執(zhí)行Runner(ApplicationRunner和CommandLineRunner)-->
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
第2章 環(huán)境變量及配置
2.1 prepareEnvironment
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// 創(chuàng)建和配置環(huán)境變量
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
listeners.environmentPrepared(bootstrapContext, environment);
DefaultPropertiesPropertySource.moveToEnd(environment);
Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
"Environment prefix cannot be set via properties.");
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
2.2 getOrCreateEnvironment
/**
* 該方法根據(jù)webApplicationType判斷當前項目是什么類型項目
*/
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
switch (this.webApplicationType) {
case SERVLET:
return new StandardServletEnvironment();
case REACTIVE:
return new StandardReactiveWebEnvironment();
default:
return new StandardEnvironment();
}
}
//webApplicationType是在new SpringApplication方法中通過WebApplicationType.deduceFromClasspath()進行賦值
枚舉WebApplicationType中定義了三個應(yīng)用類型:
- NONE:應(yīng)用程序不作為web應(yīng)用啟動,不啟動內(nèi)嵌的服務(wù)。
- SERVLET:應(yīng)用程序以基于servlet的web應(yīng)用啟動,需啟動內(nèi)嵌servlet web服務(wù)。
- REACTIVE:應(yīng)用程序以響應(yīng)式web應(yīng)用啟動,需啟動內(nèi)嵌的響應(yīng)式web服務(wù)。
這里調(diào)用new
StandardServletEnvironment()方法;
StandardServletEnvironment繼承了StandardEnvironment方法,StandardEnvironment又繼承了AbstractEnvironment方法;在AbstractEnvironment方法中調(diào)用了customizePropertySources方法
public AbstractEnvironment() {
customizePropertySources(this.propertySources);
}
customizePropertySources方法會回調(diào)
StandardServletEnvironment方法中的customizePropertySources方法,
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
}
//在這里又調(diào)用了父類StandardEnvironment的方法
super.customizePropertySources(propertySources);
}
到這里為止propertySources里面就加載了servletConfigInitParams、servletContextInitParams、systemProperties、systemEnvironment
然后回到prepareEnvironment方法中,在
listeners.environmentPrepared(bootstrapContext, environment);方法中去進行監(jiān)聽
2.3 environmentPrepared
void environmentPrepared(ConfigurableEnvironment environment) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.environmentPrepared(environment);
}
}
繼續(xù)進入environmentPrepared方法,會進入到
SpringApplicationRunListener接口,這個接口在run方法中的getRunListeners里面獲取,最終是在sprin.factories里面進行加載實現(xiàn)類EventPublishingRunListener,執(zhí)行的是EventPublishingRunListener類中的environmentPrepared方法。
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster
.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}
multicastEvent
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}
invokeListener
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
ErrorHandler errorHandler = getErrorHandler();
if (errorHandler != null) {
try {
doInvokeListener(listener, event);
}
catch (Throwable err) {
errorHandler.handleError(err);
}
}
else {
doInvokeListener(listener, event);
}
}
doInvokeListener
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
listener.onApplicationEvent(event);
}
catch (ClassCastException ex) {
String msg = ex.getMessage();
if (msg == null || matchesClassCastMessage(msg, event.getClass())) {
// Possibly a lambda-defined listener which we could not resolve the generic event type for
// -> let's suppress the exception and just log a debug message.
Log logger = LogFactory.getLog(getClass());
if (logger.isTraceEnabled()) {
logger.trace("Non-matching event type for listener: " + listener, ex);
}
}
else {
throw ex;
}
}
}
進入
ConfigFileApplicationListener實現(xiàn)類中的onApplicationEvent方法
onApplicationEvent
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
//在這個方法里面讀取配置文件
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent(event);
}
}
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);
for (EnvironmentPostProcessor postProcessor : postProcessors) {
//進入
postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
}
}
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
//進入
addPropertySources(environment, application.getResourceLoader());
}
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
RandomValuePropertySource.addToEnvironment(environment);
//load方法是讀取配置文件的核心方法
new Loader(environment, resourceLoader).load();
}
void load() {
FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY,
(defaultProperties) -> {
this.profiles = new LinkedList<>();
this.processedProfiles = new LinkedList<>();
this.activatedProfiles = false;
this.loaded = new LinkedHashMap<>();
initializeProfiles();
while (!this.profiles.isEmpty()) {
Profile profile = this.profiles.poll();
if (isDefaultProfile(profile)) {
addProfileToEnvironment(profile.getName());
}
load(profile, this::getPositiveProfileFilter,
addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}
load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
addLoadedPropertySources();
applyActiveProfiles(defaultProperties);
});
}
4.2.4 createApplicationContext
一起來看下context = createApplicationContext(); 這段代碼,這段代碼主要是根據(jù)項目類型創(chuàng)建上下文,并且會注入幾個核心組件類。
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
public AnnotationConfigServletWebServerApplicationContext(DefaultListableBeanFactory beanFactory) {
super(beanFactory);
//1:會去注入一些spring核心組件
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
Web類型項目創(chuàng)建上下文對象
AnnotationConfigServletWebServerApplicationContext 。這里會把 ConfigurationClassPostProcessor 、AutowiredAnnotationBeanPostProcessor 等一些核心組件加入到Spring容器
2.5 refreshContext
下面一起來看下refreshContext(context) 這個方法,這個方法啟動spring的代碼加載了bean,還啟動了內(nèi)置web容器
private void refreshContext(ConfigurableApplicationContext context) {
// 轉(zhuǎn)到定義看看
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
//看看refresh()方法去
((AbstractApplicationContext) applicationContext).refresh();
}
轉(zhuǎn)到
AbstractApplicationContext - >refresh()方法里面發(fā)現(xiàn)這是spring容器啟動代碼
/**
* 加載或刷新一個持久化的配置,可能是XML文件、屬性文件或關(guān)系數(shù)據(jù)庫模式。
* 由于這是一種啟動方法,如果失敗,應(yīng)該銷毀已經(jīng)創(chuàng)建的單例,以避免懸空資源。
* 換句話說,在調(diào)用該方法之后,要么全部實例化,要么完全不實例化。
* @throws 如果bean工廠無法初始化,則拋出 BeansException 異常
* @throws 如果已經(jīng)初始化且不支持多次刷新,則會拋出 IllegalStateException 異常
*/
@Override
public void refresh() throws BeansException, IllegalStateException {
//加載或刷新配置前的同步處理
synchronized (this.startupShutdownMonitor) {
// 為刷新而準備此上下文
prepareRefresh();
// 告訴子類去刷新內(nèi)部bean工廠。
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 準備好bean工廠,以便在此上下文中使用。
prepareBeanFactory(beanFactory);
try {
// 允許在上下文子類中對bean工廠進行后置處理。
postProcessBeanFactory(beanFactory);
// 調(diào)用在上下文中注冊為bean的工廠處理器。
invokeBeanFactoryPostProcessors(beanFactory);
// 注冊攔截bean創(chuàng)建的bean處理器。
registerBeanPostProcessors(beanFactory);
// 初始化此上下文的 message resource 消息資源。
initMessageSource();
// 為這個上下文初始化事件多路廣播器。
initApplicationEventMulticaster();
// 初始化特定上下文子類中的其他特殊bean。
onRefresh();
// 注冊監(jiān)聽器(檢查監(jiān)聽器的bean并注冊它們)。
registerListeners();
// 實例化所有剩余的(非 lazy-init 懶初始化的)單例。
finishBeanFactoryInitialization(beanFactory);
// 最后一步: 發(fā)布相應(yīng)的事件。
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// 銷毀已經(jīng)創(chuàng)建的單例,以避免懸空資源。
destroyBeans();
// 重置 'active' 表示.
cancelRefresh(ex);
// 將異常傳播給調(diào)用者。
throw ex;
}
finally {
// 重置Spring內(nèi)核中的共用的緩存,因為我們可能再也不需要單例bean的元數(shù)據(jù)了……
resetCommonCaches();
}
}
}
下次預告
- 通過前面三篇文章,我們學了springboot在spring的基礎(chǔ)之上優(yōu)化了bean注入IoC的流程,同時也做到了外部化配置文件的統(tǒng)一管理。接下來其實還有一個流程我們沒有走完。就是我們明白了自動裝配流程是自動加載starter組件的,那starter組件又按照springboot的約定做了什么事呢?
- 我們spring集成SpringMVC的時候需要手動創(chuàng)建一個Tomcat容器去運行,但是springboot只需要啟動main方法就可以,它內(nèi)部又做了什么事情呢?