寫在前面
隨著微服務時代的到來椎木,spring boot基本上是所有java開發(fā)人員標配的技術(shù)棧了窄驹,我們幾乎每天都在和spring打著交到朝卒,所以深入理解spring的理念和細節(jié)就十分重要。但是馒吴,不得不說spring是一個龐大的體系扎运,龐大到幾乎所有的組件,spring都會有一套對應的解決方案饮戳。雖然寫一個系統(tǒng)的深入spring的文章已經(jīng)計劃了很久豪治,但是由于它的龐大和復雜,讓我一直找不到頭緒扯罐。所以我嘗試著一點一點的將spring的核心鏈路整理成一個系列文章负拟,一是讓自己把spring系統(tǒng)的學習一遍,二也是想尋找志同道合的人一起學習和討論歹河,相互提高嘛掩浙。
Spring Boot啟動
入口
對于spring boot的啟動方式分為兩種,第一種是通過fatjar的方式進行啟動秸歧,另一個是通過IED啟動厨姚。本文主要關(guān)注Springboot啟動流程,對于fatjar的啟動方式后面單獨介紹键菱。
spring boot的啟動十分簡單谬墙,當然這就是它的初衷,配置簡單化:
@SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
靜態(tài)方法run经备,創(chuàng)建了一個SpringApplication并運行它拭抬,這里用靜態(tài)方法創(chuàng)建實例并運行還是比較清晰(至少比調(diào)用構(gòu)造函數(shù)的好):
/**
* Static helper that can be used to run a {@link SpringApplication} from the
* specified sources using default settings and user supplied arguments.
* @param sources the sources to load
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link ApplicationContext}
*/
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
return new SpringApplication(sources).run(args);
}
spring boot的準備工作
構(gòu)造函數(shù)對SpringApplication做了一個初始化,主要是構(gòu)建Spring的基本運行環(huán)境:
private void initialize(Object[] sources) {
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
this.webEnvironment = deduceWebEnvironment();
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
deduceWebEnvironment主要是判斷當前環(huán)境是否為web環(huán)境侵蒙,因為web環(huán)境要做一些更多的初始化造虎,所以判斷還是有必要的。
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
private boolean deduceWebEnvironment() {
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return false;
}
}
return true;
}
setInitializers 主要是獲取當前環(huán)境下配置的ApplicationContextInitializer纷闺,方法是通過classloader讀取當前環(huán)境下所有的META-INF/spring.factories文件中的配置算凿,來進行實例化份蝴。這里classLoader可以獲取多個spring.factories配置,我們也可以使用這個特性去做一些擴展:
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<String>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
try {
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List<String> result = new ArrayList<String>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
"] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
像Spring boot本身定義的spring.factories:
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.context.web.ServerPortInfoApplicationContextInitializer
然后通過反射進行實例化:
private <T> List<T> createSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
Set<String> names) {
List<T> instances = new ArrayList<T>(names.size());
for (String name : names) {
try {
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
Constructor<?> constructor = instanceClass
.getDeclaredConstructor(parameterTypes);
T instance = (T) BeanUtils.instantiateClass(constructor, args);
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException(
"Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}
然后保存造SpringApplication的成員變量中:
/**
* Sets the {@link ApplicationContextInitializer} that will be applied to the Spring
* {@link ApplicationContext}.
* @param initializers the initializers to set
*/
public void setInitializers(
Collection<? extends ApplicationContextInitializer<?>> initializers) {
this.initializers = new ArrayList<ApplicationContextInitializer<?>>();
this.initializers.addAll(initializers);
}
同樣的澎媒,加載ApplicationContextInitializer之后對ApplicationListener進行了加載,下面是SpringBoot本身的Listener:
# 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.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener,\
org.springframework.boot.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.logging.LoggingApplicationListener
/**
* Sets the {@link ApplicationListener}s that will be applied to the SpringApplication
* and registered with the {@link ApplicationContext}.
* @param listeners the listeners to set
*/
public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
this.listeners = new ArrayList<ApplicationListener<?>>();
this.listeners.addAll(listeners);
}
啟動spring boot
一切就緒后搞乏,調(diào)用run方法,啟動Spring Boot:
/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
*/
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.started();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
analyzers = new FailureAnalyzers(context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
listeners.finished(context, null);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, analyzers, ex);
throw new IllegalStateException(ex);
}
}
SpringBoot啟動事件發(fā)送
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.started();
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));
}
這里面代碼其實比較熟悉了戒努,就是獲取SpringApplicationRunListener请敦,構(gòu)建SpringApplicationRunListeners,并發(fā)送started事件储玫,那么整個事件發(fā)送給誰侍筛?
其實SpringApplicationRunListener 在spring boot中配置的只有EventPublishingRunListener:
public EventPublishingRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
this.initialMulticaster = new SimpleApplicationEventMulticaster();
for (ApplicationListener<?> listener : application.getListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
@Override
public void started() {
this.initialMulticaster
.multicastEvent(new ApplicationStartedEvent(this.application, this.args));
}
其實也就是將事件發(fā)送給前面我們初始化的Listeners:
# 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.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener,\
org.springframework.boot.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.logging.LoggingApplicationListener
當然不是通知所有的Listeners,Springboot這里會根據(jù)event和listener類型做一個過濾:
/**
* Return a Collection of ApplicationListeners matching the given
* event type. Non-matching listeners get excluded early.
* @param event the event to be propagated. Allows for excluding
* non-matching listeners early, based on cached matching information.
* @param eventType the event type
* @return a Collection of ApplicationListeners
* @see org.springframework.context.ApplicationListener
*/
protected Collection<ApplicationListener<?>> getApplicationListeners(
ApplicationEvent event, ResolvableType eventType) {
Object source = event.getSource();
Class<?> sourceType = (source != null ? source.getClass() : null);
ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);
// Quick check for existing entry on ConcurrentHashMap...
ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
if (retriever != null) {
return retriever.getApplicationListeners();
}
if (this.beanClassLoader == null ||
(ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
(sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
// Fully synchronized building and caching of a ListenerRetriever
synchronized (this.retrievalMutex) {
retriever = this.retrieverCache.get(cacheKey);
if (retriever != null) {
return retriever.getApplicationListeners();
}
retriever = new ListenerRetriever(true);
Collection<ApplicationListener<?>> listeners =
retrieveApplicationListeners(eventType, sourceType, retriever);
this.retrieverCache.put(cacheKey, retriever);
return listeners;
}
}
else {
// No ListenerRetriever caching -> no synchronization necessary
return retrieveApplicationListeners(eventType, sourceType, null);
}
}
這里一個會被調(diào)用的LoggingApplicationListener就是一個比較重要的Listener。
environment構(gòu)建
spring 在啟動之前會讀取環(huán)境變量撒穷、jvm啟動參數(shù)和自身的配置文件匣椰,加載這些配置文件到environment:
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
listeners.environmentPrepared(environment);
if (isWebEnvironment(environment) && !this.webEnvironment) {
environment = convertToStandardEnvironment(environment);
}
return environment;
}
首先加載環(huán)境變量和JVM啟動參數(shù):
/**
* Create a new {@code Environment} instance, calling back to
* {@link #customizePropertySources(MutablePropertySources)} during construction to
* allow subclasses to contribute or manipulate {@link PropertySource} instances as
* appropriate.
* @see #customizePropertySources(MutablePropertySources)
*/
public AbstractEnvironment() {
customizePropertySources(this.propertySources);
if (this.logger.isDebugEnabled()) {
this.logger.debug(String.format(
"Initialized %s with PropertySources %s", getClass().getSimpleName(), this.propertySources));
}
}
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
如果為WEB環(huán)境,那么會加載Servlet相關(guān)的配置:
@Override
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));
}
super.customizePropertySources(propertySources);
}
接下來加載spring的配置:
protected void configureEnvironment(ConfigurableEnvironment environment,
String[] args) {
configurePropertySources(environment, args);
configureProfiles(environment, args);
}
通過configureProfiles加載Profile:
protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
environment.getActiveProfiles(); // ensure they are initialized
// But these ones should go first (last wins in a property key clash)
Set<String> profiles = new LinkedHashSet<String>(this.additionalProfiles);
profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
environment.setActiveProfiles(profiles.toArray(new String[profiles.size()]));
}
environment.getActiveProfiles在Environment中獲取Profile的配置(讀取環(huán)境變量中的spring.profiles.active配置):
protected Set<String> doGetActiveProfiles() {
synchronized (this.activeProfiles) {
if (this.activeProfiles.isEmpty()) {
String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
if (StringUtils.hasText(profiles)) {
setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
StringUtils.trimAllWhitespace(profiles)));
}
}
return this.activeProfiles;
}
}
然后把additionalProfiles和activeProfiles做一個合并端礼,set到ActiveProfiles中禽笑。
上面主要是對當前環(huán)境指定的Profile做一個匯總,加載是通過發(fā)送ApplicationEnvironmentPreparedEvent給對應的ConfigFileApplicationListener進行Profile解析:
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());
}
}
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
addPropertySources(environment, application.getResourceLoader());
configureIgnoreBeanInfo(environment);
bindToSpringApplication(environment, application);
}
addPropertySources中對Profile進行加載蛤奥,獲取之前加載的Profile和default Profile:
public void load() {
this.propertiesLoader = new PropertySourcesLoader();
this.activatedProfiles = false;
this.profiles = Collections.asLifoQueue(new LinkedList<Profile>());
this.processedProfiles = new LinkedList<Profile>();
// Pre-existing active profiles set via Environment.setActiveProfiles()
// are additional profiles and config files are allowed to add more if
// they want to, so don't call addActiveProfiles() here.
Set<Profile> initialActiveProfiles = initializeActiveProfiles();
this.profiles.addAll(getUnprocessedActiveProfiles(initialActiveProfiles));
if (this.profiles.isEmpty()) {
for (String defaultProfileName : this.environment.getDefaultProfiles()) {
Profile defaultProfile = new Profile(defaultProfileName, true);
if (!this.profiles.contains(defaultProfile)) {
this.profiles.add(defaultProfile);
}
}
}
// The default profile for these purposes is represented as null. We add it
// last so that it is first out of the queue (active profiles will then
// override any settings in the defaults when the list is reversed later).
this.profiles.add(null);
while (!this.profiles.isEmpty()) {
Profile profile = this.profiles.poll();
for (String location : getSearchLocations()) {
if (!location.endsWith("/")) {
// location is a filename already, so don't search for more
// filenames
load(location, null, profile);
}
else {
for (String name : getSearchNames()) {
load(location, name, profile);
}
}
}
this.processedProfiles.add(profile);
}
addConfigurationProperties(this.propertiesLoader.getPropertySources());
}
getSearchLocations獲取Profile的搜索路徑佳镜,這里可以通過spring.config.location配置,來新增搜索路徑凡桥,除了設(shè)置路徑還有幾個默認的搜索路徑(classpath:/,classpath:/config/,file:./,file:./config/)
private Set<String> getSearchLocations() {
Set<String> locations = new LinkedHashSet<String>();
// User-configured settings take precedence, so we do them first
if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {
for (String path : asResolvedSet(
this.environment.getProperty(CONFIG_LOCATION_PROPERTY), null)) {
if (!path.contains("$")) {
path = StringUtils.cleanPath(path);
if (!ResourceUtils.isUrl(path)) {
path = ResourceUtils.FILE_URL_PREFIX + path;
}
}
locations.add(path);
}
}
locations.addAll(
asResolvedSet(ConfigFileApplicationListener.this.searchLocations,
DEFAULT_SEARCH_LOCATIONS));
return locations;
}
getSearchNames獲取搜索的文件名稱蟀伸,這里可以通過spring.config.name設(shè)置配置名稱,這里也有一個默認的配置名稱application缅刽,默認搜索application.properties文件:
private Set<String> getSearchNames() {
if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {
return asResolvedSet(this.environment.getProperty(CONFIG_NAME_PROPERTY),
null);
}
return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
}
然后做Profile的加載啊掏,上文可以看到一句this.profiles.add(null),這一句主要為了加載默認的Profile配置衰猛。還有一個Profile 名為default迟蜜,主要是為了加載application-default.properties。這里的文件命名規(guī)則都是進行默認拼接的:location + name + "-" + profile + "." + ext
private void load(String location, String name, Profile profile) {
String group = "profile=" + (profile == null ? "" : profile);
if (!StringUtils.hasText(name)) {
// Try to load directly from the location
loadIntoGroup(group, location, profile);
}
else {
// Search for a file with the given name
for (String ext : this.propertiesLoader.getAllFileExtensions()) {
if (profile != null) {
// Try the profile-specific file
loadIntoGroup(group, location + name + "-" + profile + "." + ext,
null);
for (Profile processedProfile : this.processedProfiles) {
if (processedProfile != null) {
loadIntoGroup(group, location + name + "-"
+ processedProfile + "." + ext, profile);
}
}
// Sometimes people put "spring.profiles: dev" in
// application-dev.yml (gh-340). Arguably we should try and error
// out on that, but we can be kind and load it anyway.
loadIntoGroup(group, location + name + "-" + profile + "." + ext,
profile);
}
// Also try the profile-specific section (if any) of the normal file
loadIntoGroup(group, location + name + "." + ext, profile);
}
}
}
這里值得一提的是啡省,對于application.properties中也可以對配置文件進行配置娜睛,然后新增一個Profile:
private void handleProfileProperties(PropertySource<?> propertySource) {
Set<Profile> activeProfiles = getProfilesForValue(
propertySource.getProperty(ACTIVE_PROFILES_PROPERTY));
maybeActivateProfiles(activeProfiles);
Set<Profile> includeProfiles = getProfilesForValue(
propertySource.getProperty(INCLUDE_PROFILES_PROPERTY));
addProfiles(includeProfiles);
}
通過在application.properties中定義spring.profiles.active,可以加載指定的application-${spring.profiles.active}的配置文件
/**
* The "active profiles" property name.
*/
public static final String ACTIVE_PROFILES_PROPERTY = "spring.profiles.active";
/**
* The "includes profiles" property name.
*/
public static final String INCLUDE_PROFILES_PROPERTY = "spring.profiles.include";
創(chuàng)建ApplicationContext
createApplicationContext通過判斷是否為Web環(huán)境來創(chuàng)建ApplicationContext冕杠,非web環(huán)境為:AnnotationConfigApplicationContext,web環(huán)境為:AnnotationConfigEmbeddedWebApplicationContext酸茴。
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
contextClass = Class.forName(this.webEnvironment
? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, "
+ "please specify an ApplicationContextClass",
ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}
這里初始換ApplicationContext是很重要的分预,因為在實例化ApplicationContext的時候注冊了很多的Processor,這些Processer被用來處理各種@Annotation薪捍,這里主要涉及Spring的實現(xiàn)笼痹,后面會專門針對Spring的Bean加載再寫一些篇專題文章配喳,這里就不展開了。
上下文準備:
prepareContext主要是做ApplicationContext refresh前的一些準備工作凳干,例如調(diào)用所有的Initializers晴裹,來做些初始化的工作,同時加載啟動類救赐,并發(fā)送一些事件給到對應監(jiān)聽的listener
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);//將Environment傳遞到上下文中
postProcessApplicationContext(context);
applyInitializers(context);//調(diào)用前文中聲明的Initializers,Initializer大多是注冊Listener或Processor到ApplictionContext涧团。
listeners.contextPrepared(context);//發(fā)送contextPreparedEvent
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
if (printedBanner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}
// Load the sources
Set<Object> sources = getSources();//獲取SpringApplication的source成員,一般為springboot的啟動類经磅。
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[sources.size()]));//加載啟動類到ApplicationContext泌绣,后續(xù)利用source類加載其他聲明的bean
listeners.contextLoaded(context);//發(fā)送ContextLoaded
}
這里值得一提的是Load Resource,Load Resource主要是用來加載Spring boot的啟動類预厌,啟動類上一般會標識@Import阿迈、scanPackage等等一些配置,這些是整個Spring容器加載Bean需要的入口類轧叽。
refreshContext
refreshContext不多說苗沧,主要是refresh ApplicationContext ,里面邏輯十分復雜炭晒,很難一下子說全說透待逞,本文主要是針對springBoot的啟動流程,關(guān)于Spring的啟動和加載腰埂,后續(xù)再寫一些專題討論:
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
finished
這一步主要是做Spring初始化之后的回調(diào)和通知工作飒焦,afterRefresh通過調(diào)用BeanFactory中定義的ApplicationRunner和CommandLineRunner來做context初始化之后的邏輯,listeners.finished主要是通知監(jiān)聽的Listeners屿笼。
至此牺荠,Spring boot已經(jīng)啟動完成。
總結(jié)
Spring Boot的啟動流程是比較長的驴一,但是也還是給我們留下了很多的類似于SPI的擴展點去實現(xiàn)我們的功能休雌,尤其是Initializer,除了允許我們可以在ApplicationContext沒有Refresh的時候做一些初始化的事情肝断,同時還提供了一個無侵入的方式將我們自己插件(starter)中需要用到的processor和Listener等擴展注入到ApplicationContext中杈曲。
在整個啟動過程中Spring boot會有一些Event發(fā)出,這也就給了我們在不同階段實現(xiàn)自己的擴展邏輯的機會胸懈,實現(xiàn)起來也比較簡單担扑,監(jiān)聽Event即可。
除了Spring本身的功能配置的初始化之外趣钱,Spring Boot還要做Spring容器的初始化涌献,因為Spring容器的初始化過程過于復雜,不能在這張圖中展示出來首有。后面會拿出專題來寫一下Spring的內(nèi)部的運轉(zhuǎn)機制燕垃。
后續(xù)計劃
Spring Boot的啟動流程算是一個開頭枢劝,對于諸多的擴展Initializer、Listener以及Spring的內(nèi)容都沒有詳細的寫卜壕,畢竟Sping太大您旁,內(nèi)容太多,不是三言兩語能說明白的轴捎。后面會把整個流程的各個節(jié)點單獨的拆開來詳細的學習一下鹤盒,循序漸進的把Spring系統(tǒng)的學習一下。
當然也不只是分析源碼轮蜕,之所以然更要知其所以然昨悼,對于好的思想和實現(xiàn),理解的同時加以實踐才是最好的跃洛。