springboot 版本為 : 2.2.3
概述
spring boot 的啟動過程主要是兩個方面,一個是創建 SpringApplication 這個類,該類用於啟動啟動整個應用,是應用的啟動類。另一方面是 SpringApplication 的 run 方法,該方法會初始化 listener 和 initialize ,並在分發事件到各個 listeners ,同時從資源中加載bean, 其中 listeners 和 initializer 兩個類是用於 spring boot 的啟動擴展
@SpringBootApplication 注解
SpringApplication.run(DownhtmlApplication.class, args);
調用 SpringApplication 的 run 方法,會創建一個SpringApplication自身的一個對象,然后再調用 run方法。run方法會返回一個 ConfigurableApplicationContext 。
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); }
看一下這個 方法是干什么的 ?
Create a new {@link SpringApplication} instance. The application context will load beans from the specified primary sources (see {@link SpringApplication class-level documentation for details. The instance can be customized before calling {@link #run(String...)}.
這個 application context 將會加載特定優先級別資源的bean,同時在調用run之前都會執行自定義的方法 初始化方法就是為字段賦值。 我們再看一下 SpringApplication 的注釋 :
Class that can be used to bootstrap and launch a Spring application from a Java main method. By default class will perform the following steps to bootstrap your application: - Create an appropriate ApplicationContext instance (depending on your classpath) - Register a CommandLinePropertySource to expose command line arguments as Spring properties - Refresh the application context, loading all singleton beans - Trigger any CommandLineRunner beans In most circumstances the static run(Class, String[]) method can be called directly from your main method to bootstrap your application: @Configuration @EnableAutoConfiguration public class MyApplication { // ... Bean definitions public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } } For more advanced configuration a SpringApplication instance can be created and customized before being run: public static void main(String[] args) { SpringApplication application = new SpringApplication(MyApplication.class); // ... customize application settings here application.run(args) } SpringApplications can read beans from a variety of different sources. It is generally recommended that a single @Configuration class is used to bootstrap your application, however, you may also set sources from: - The fully qualified class name to be loaded by AnnotatedBeanDefinitionReader - The location of an XML resource to be loaded by XmlBeanDefinitionReader, or a groovy script to be loaded by GroovyBeanDefinitionReader - The name of a package to be scanned by ClassPathBeanDefinitionScanner Configuration properties are also bound to the SpringApplication. This makes it possible to set SpringApplication properties dynamically, like additional sources ("spring.main.sources" - a CSV list) the flag to indicate a web environment ("spring.main.web-application-type=none") or the flag to switch off the banner ("spring.main.banner-mode=off").
有點長,但是很好理解,核心的就是根據你的 classpath 創建合適的 ApplicationContext 實例,在一個就是刷新 application context 加載所有的單例 bean 。
從字面上也可以理解Application(例如上面的 SpringApplications)和 ApplicationContext 是一種提供的關系,ApplicationContext 更加抽象一點(應用上下文),它是由 Application 產生。
SpringApplication構造函數
下面 SpringApplication 的構造函數主要做了一下幾個事情:
- 推斷應用類型是 standard 還是 web
- 設置初始化器(Initializer)
- 設置監聽器(Listener)
- 推斷應用入口類
/** * Create a new {@link SpringApplication} instance. The application context will load * beans from the specified primary sources (see {@link SpringApplication class-level} * documentation for details. The instance can be customized before calling * {@link #run(String...)}. * @param resourceLoader the resource loader to use * @param primarySources the primary bean sources * @see #run(Class, String[]) * @see #setSources(Set) * */ @SuppressWarnings({ "unchecked", "rawtypes" }) public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); this.webApplicationType = WebApplicationType.deduceFromClasspath(); setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass(); }
設置初始化器(Initializer)
這個步驟來說就是加載 ApplicationContextInitializer 的子類實現。
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) { return getSpringFactoriesInstances(type, new Class<?>[] {}); } private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { //加載 classLoader ClassLoader classLoader = getClassLoader(); //set 保存實例保持實例唯一 // Use names and ensure unique to protect against duplicates Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); //創建實例 List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); //排序返回 AnnotationAwareOrderComparator.sort(instances); return instances; }
那么加載這個 ApplicationContextInitializer 是干什么的呢
在Spring上下文被刷新之前進行初始化的操作。典型地比如在Web應用中,注冊Property Sources或者是激活Profiles。Property Sources比較好理解,就是配置文件。Profiles是Spring為了在不同環境下(如DEV,TEST,PRODUCTION等),加載不同的配置項而抽象出來的一個實體。
項目中的 profiles : application.properties 文件下
spring.profiles.active=test #spring.profiles.include: prod,dev,test,ppod_dev
設置監聽器(Listener)
我們可以看到設置監聽器(Listener)也是調用了 getSpringFactoriesInstances 方法 ,不同的是這次傳入的類是 ApplicationListener 。 我們看一下這個 ApplicationListener 是什么東西 ?
@FunctionalInterface public interface ApplicationListener<E extends ApplicationEvent> extends EventListener { /** * Handle an application event. * @param event the event to respond to */ void onApplicationEvent(E event); }
Interface to be implemented by application event listeners. Based on the standard java.util.EventListener interface for the Observer design pattern. As of Spring 3.0, an ApplicationListener can generically declare the event type that it is interested in. When registered with a Spring ApplicationContext, events will be filtered accordingly, with the listener getting invoked for matching event objects only.
給application event listener 提供的一個接口,基於java.util.EventListener 的原生實現。 Spring 3.0 以后可以定義和注冊事件到 ApplicationContext 中去, SpringApplication 提供了方法 ,例如 :
SpringApplication springApplication = new SpringApplication(); ApplicationListener listener = new ApplicationListener() { @Override public void onApplicationEvent(ApplicationEvent event) { } }; springApplication.addListeners(listener); springApplication.run();
關於監聽器還可以查看參考資料中的文章。
SpringApplication run 方法。
源碼簡要分析
/** * 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; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); // 設置java.awt.headless系統屬性為true - 沒有圖形化界面 configureHeadlessProperty(); // 獲取 listener,並且啟動ing listener SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { //准備環境參數 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); //banner 展示 Banner printedBanner = printBanner(environment); //創建 context context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); //准備 context prepareContext(context, environment, listeners, applicationArguments, printedBanner); //刷新 context refreshContext(context); //鈎子方法 afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); } //listener 傳入 context 形參,正式啟動 listeners.started(context); 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; }
獲取 SpringApplicationRunListeners 對象的方法 。
private SpringApplicationRunListeners getRunListeners(String[] args) { Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class }; return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)); }
那么 SpringApplicationRunListeners 個是什么呢?SpringApplicationRunListeners 持有一個 SpringApplicationRunListener 的列表,實際上就是對各種時間轉發到多個 SpringApplicationRunListener 上
class SpringApplicationRunListeners { private final Log log; private final List<SpringApplicationRunListener> listeners; ... }
SpringApplicationRunListener 從名字就可以看到是監聽器,監聽各種事件,包括 contextprepare 等等。
createApplicationContext 方法創建 ConfigurableApplicationContext 對象,利用發射創建特定的 context 。
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); }
后面兩個方法 prepareContext 和 refreshContext 進行了計較多的邏輯,refreshContext 方法此處不再分析 。
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { context.setEnvironment(environment); postProcessApplicationContext(context); applyInitializers(context); listeners.contextPrepared(context); if (this.logStartupInfo) { logStartupInfo(context.getParent() == null); logStartupProfileInfo(context); } // Add boot specific singleton beans 加上兩個特別的 root 單例類 ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); beanFactory.registerSingleton("springApplicationArguments", applicationArguments); if (printedBanner != null) { beanFactory.registerSingleton("springBootBanner", printedBanner); } if (beanFactory instanceof DefaultListableBeanFactory) { ((DefaultListableBeanFactory) beanFactory) .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); } if (this.lazyInitialization) { context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor()); } // Load the sources 加載資源 Set<Object> sources = getAllSources(); Assert.notEmpty(sources, "Sources must not be empty"); load(context, sources.toArray(new Object[0])); listeners.contextLoaded(context); } //賦值context 中的字段 protected void postProcessApplicationContext(ConfigurableApplicationContext context) { if (this.beanNameGenerator != null) { context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, this.beanNameGenerator); } if (this.resourceLoader != null) { if (context instanceof GenericApplicationContext) { ((GenericApplicationContext) context).setResourceLoader(this.resourceLoader); } if (context instanceof DefaultResourceLoader) { ((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader()); } } if (this.addConversionService) { context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance()); } } // 可以看到重要的邏輯就是執行 initializer 的 initialize 方法 protected void applyInitializers(ConfigurableApplicationContext context) { for (ApplicationContextInitializer initializer : getInitializers()) { Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(), ApplicationContextInitializer.class); Assert.isInstanceOf(requiredType, context, "Unable to call initializer."); initializer.initialize(context); } } /** * load方法進行了重要的邏輯,加載 bean 進入到 context 中 * Load beans into the application context. * @param context the context to load beans into * @param sources the sources to load */ protected void load(ApplicationContext context, Object[] sources) { if (logger.isDebugEnabled()) { logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources)); } 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(); }
補充
來自參考資料的總結 :
本文分析了Spring Boot啟動時的關鍵步驟,主要包含以下兩個方面:
- SpringApplication實例的構建過程
其中主要涉及到了初始化器(Initializer)以及監聽器(Listener)這兩大概念,它們都通過META-INF/spring.factories完成定義。
- SpringApplication實例run方法的執行過程
其中主要有一個SpringApplicationRunListeners的概念,它作為Spring Boot容器初始化時各階段事件的中轉器,將事件派發給感興趣的Listeners(在SpringApplication實例的構建過程中得到的)。這些階段性事件將容器的初始化過程給構造起來,提供了比較強大的可擴展性。
如果從可擴展性的角度出發,應用開發者可以在Spring Boot容器的啟動階段,擴展哪些內容呢:
- 初始化器(Initializer)
- 監聽器(Listener)
- 容器刷新后置Runners(ApplicationRunner或者CommandLineRunner接口的實現類)
參考資料
- https://my.oschina.net/dslcode/blog/1591523 (springboot 中 ApplicationListener 相關)
- https://blog.csdn.net/dm_vincent/article/details/76735888