學習過springboot的都知道,在Springboot的main入口函數中調用SpringApplication.run(DemoApplication.class,args)函數便可以啟用SpringBoot應用程序,跟蹤一下SpringApplication源碼可以發現,最終還是調用了SpringApplication的動態run函數。
下面以SpringBoot2.0.3.RELEASE為例簡單分析一下運行過程。
SpringApplicatiton部分源碼:
1 public static ConfigurableApplicationContext run(Class<?>[] primarySources, 2 String[] args) { 3 //創建springapplication對象,調用函數run(args) 4 return new SpringApplication(primarySources).run(args); 5 }
上面的源碼可以發現還是先創建SpringApplication實例,再調用run方法
第一步 分析 SpringApplication構造函數
SpringApplication構造函數代碼如下:
1 public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { 2 this.resourceLoader = resourceLoader; 3 Assert.notNull(primarySources, "PrimarySources must not be null"); 4 this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); 5 6 //1:判斷web環境 7 this.webApplicationType = deduceWebApplicationType(); 8 9 //2:加載classpath下META-INF/spring.factories中配置的ApplicationContextInitializer 10 setInitializers((Collection) getSpringFactoriesInstances( 11 ApplicationContextInitializer.class)); 12 //3:加載classpath下META-INF/spring.factories中配置的ApplicationListener 13 14 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); 15 //4:推斷main方法所在的類 16 this.mainApplicationClass = deduceMainApplicationClass(); 17 }
具體邏輯分析:
- deduceWebApplicationType(), SpringApplication構造函數中首先初始化應用類型,根據加載相關類路徑判斷應用類型,具體邏輯如下:
1 private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework." 2 + "web.reactive.DispatcherHandler"; 3 4 private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework." 5 + "web.servlet.DispatcherServlet"; 6 7 private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet", 8 "org.springframework.web.context.ConfigurableWebApplicationContext" }; 9 10 11 12 private WebApplicationType deduceWebApplicationType() { 13 //當類路徑中存在REACTIVE_WEB_ENVIRONMENT_CLASS並且不存在MVC_WEB_ENVIRONMENT_CLASS時 14 if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null) 15 && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) { 16 return WebApplicationType.REACTIVE; 17 } 18 //當加載的類路徑中不包含WEB_ENVIRONMENT_CLASSES中定義的任何一個類時,返回標准應用() 19 for (String className : WEB_ENVIRONMENT_CLASSES) { 20 if (!ClassUtils.isPresent(className, null)) {
22 return WebApplicationType.NONE; 23 } 24 } 25 //加載的類路徑中包含了WEB_ENVIRONMENT_CLASSES中定義的所有類型則判斷為servlet的web應用 26 return WebApplicationType.SERVLET; 27 }
2. setInitializers初始化屬性initializers,加載classpath下META-INF/spring.factories中配置的ApplicationContextInitializer,此處getSpringFactoriesInstances方法入參type=ApplicationContextInitializer.class
1 private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, 2 Class<?>[] parameterTypes, Object... args) { 3 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 4 // Use names and ensure unique to protect against duplicates 5 // SpringFactoriesLoader.loadFactoryNames()方法將會從calssptah下的META-INF/spring.factories中讀取key為//org.springframework.context.ApplicationContextInitializer的值,並以集合形式返回 6 Set<String> names = new LinkedHashSet<>( 7 SpringFactoriesLoader.loadFactoryNames(type, classLoader)); 8 //根據返回names集合逐個實例化,也就是初始化各種ApplicationContextInitializer,這些Initializer實際是在Spring上下文ApplicationContext執行refresh前調用 9 List<T> instances = createSpringFactoriesInstances(type, parameterTypes, 10 classLoader, args, names); 11 AnnotationAwareOrderComparator.sort(instances); //對instance排序 12 return instances; 13 }
以我的demo為例,實際debug時得到的initializers如下,其中數據來源於spring-boot,spring-boot-autoconfiguration和spring-boot-devtolls三個jar包下的classpath中,ApplicationContextInitializer接口是Spring框架提供地的,其主要作用是在Spring容器初始化過程中prepareContext()這一步進行回調,具體可參考:Spring Boot(七)擴展分析
3. setListeners 初始化屬性listeners,加載classpath下META-INF/spring.factories中配置的ApplicationListener,此處入參為getSpringFactoriesInstances方法入參type= ApplicationListener.class
1 private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, 2 Class<?>[] parameterTypes, Object... args) { 3 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 4 // Use names and ensure unique to protect against duplicates 5 // SpringFactoriesLoader.loadFactoryNames()方法將會從calssptah下的META-INF/spring.factories中讀取key為//org.springframework.context.ApplicationListener的值,並以集合形式返回 6 Set<String> names = new LinkedHashSet<>( 7 SpringFactoriesLoader.loadFactoryNames(type, classLoader)); 8 //根據配置,初始化各種ApplicationListener,作用是用來監聽ApplicationEvent 9 List<T> instances = createSpringFactoriesInstances(type, parameterTypes, 10 classLoader, args, names); 11 AnnotationAwareOrderComparator.sort(instances); 12 return instances; 13 }
第二步 分析 SpringApplication中 run方法
SpringApplication的run方法代碼如下:
1 public ConfigurableApplicationContext run(String... args) { 2 StopWatch stopWatch = new StopWatch(); 3 stopWatch.start(); 4 ConfigurableApplicationContext context = null; 5 Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); 6 //設置系統變量java.awt.headless 7 configureHeadlessProperty(); 8 //1:獲取監聽器:加載classpath下面的META-INF/spring.factories配置的監聽器SpringApplicationRunListener 9 SpringApplicationRunListeners listeners = getRunListeners(args); 10 //2:啟動監聽器:執行所有runlistener的starting方法,實際上發布一個【ApplicationStartingEvent】事件 11 listeners.starting(); 12 try { 13 //3:實例化ApplicationArguments對象 14 ApplicationArguments applicationArguments = new DefaultApplicationArguments( 15 args); 16 //4: 准備應用上下文環境Environment (web環境 or 標准環境)+配置Environment,主要是把run方法的參數配置到Environment 發布【ApplicationEnvironmentPreparedEvent】事件 17 ConfigurableEnvironment environment = prepareEnvironment(listeners, 18 applicationArguments); 19 configureIgnoreBeanInfo(environment); 20 //打印banner,SpringBoot啟動時,控制台輸出的一個歪歪扭扭的很不清楚的Spring幾個大字母,也可以自定義 21 Banner printedBanner = printBanner(environment); 22 //5: 根據不同environment實例化上下文 context 23 context = createApplicationContext(); 24 // 異常處理,實例化一個SpringBootExceptionReporter.class 用於處理啟動過程中的錯誤 25 exceptionReporters = getSpringFactoriesInstances( 26 SpringBootExceptionReporter.class, 27 new Class[] { ConfigurableApplicationContext.class }, context); 28 //6: 上下文相關預處理 發布【ApplicationPreparedEvent】事件 29 prepareContext(context, environment, listeners, applicationArguments, 30 printedBanner); 31 //7: 【刷新應用上線文】執行spring容器(context)的refresh方法,並且調用context的registerShutdownHook方法 32 refreshContext(context); 33 //8:空方法,用於擴展 34 afterRefresh(context, applicationArguments); 35 stopWatch.stop(); 36 if (this.logStartupInfo) { 37 new StartupInfoLogger(this.mainApplicationClass) 38 .logStarted(getApplicationLog(), stopWatch); 39 } 40 //9:執行所有runlisteners的started方法,發布【ApplicationStartedEvent】事件 41 listeners.started(context); 42 //10: 遍歷執行CommandLineRunner和ApplicationRunner 43 //如果需要在SpringBoot應用啟動后運行一些特殊的邏輯,可以通過實現ApplicationRunner或CommandLineRunner接口中的run方法,該自定義類的run方法會在此處統一調用 44 callRunners(context, applicationArguments); 45 } 46 catch (Throwable ex) { 47 handleRunFailure(context, ex, exceptionReporters, listeners); 48 throw new IllegalStateException(ex); 49 } 50 51 try { 52 listeners.running(context); 53 } 54 catch (Throwable ex) { 55 handleRunFailure(context, ex, exceptionReporters, null); 56 throw new IllegalStateException(ex); 57 } 58 return context; 59 }
具體分析:
1. 獲取監聽器:getRunListeners(args) 加載各種SpringApplicationRunListener實例,內部實現也還是通過SpringFactoriesLoader.loadFactoryNames(type, classLoader))實現,加載META-INF/spring.factories中key為org.springframework.boot.SpringApplicationRunListener的值,生成對應實例,SpringBoot實際加載了一個EventPublishingRunListener監聽器,該監聽器繼承SpringApplicationRunListener接口,SpringApplicationRunListener規定了SpringBoot的生命周期,在各個生命周期廣播相應的事件,調用實際的ApplicationListener類。
2. 啟動監聽器: listeners.starting() 執行所有SpringApplicationRunListener的stating方法,發布ApplicationStartedEvent事件,該事件被ApplicationListener類型的listener監聽
3. 實例化ApplicationArguments對象
4 . 准備應用上下文環境 並發布ApplicationEnvironmentPreparedEvent事件
1 private ConfigurableEnvironment prepareEnvironment( 2 SpringApplicationRunListeners listeners, 3 ApplicationArguments applicationArguments) { 4 // Create and configure the environment 5 ConfigurableEnvironment environment = getOrCreateEnvironment(); 6 //根據properties和profiles配置環境 7 configureEnvironment(environment, applicationArguments.getSourceArgs()); 8 // 執行EventPublishingRunListener發布ApplicationEnvironmentPreparedEvent事件,將會被ApplicationListener監聽到 9 listeners.environmentPrepared(environment); 10 // 11 bindToSpringApplication(environment); 12 if (this.webApplicationType == WebApplicationType.NONE) { 13 environment = new EnvironmentConverter(getClassLoader()) 14 .convertToStandardEnvironmentIfNecessary(environment); 15 } 16 ConfigurationPropertySources.attach(environment); 17 return environment; 18 }
備注:實際上載spring-boot-2.0.3.RELEASE.jar包中,可以發現spring.factories中只配置了一個RunListener: org.springframework.boot.context.event.EventPublishingRunListener
截取EventPublishingRunListener.java部分代碼:
1 public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered { 2 3 4 public EventPublishingRunListener(SpringApplication application, String[] args) { 5 this.application = application; 6 this.args = args; 7 this.initialMulticaster = new SimpleApplicationEventMulticaster(); 8 //將SpringApplication實例中的ApplicationListener類型的listeners添加到initialMulticaster,后續執行監聽 9 for (ApplicationListener<?> listener : application.getListeners()) { 10 this.initialMulticaster.addApplicationListener(listener); 11 } 12 } 13 14 // 發布一個ApplicationEnvironmentPreparedEvent事件 15 @Override 16 public void environmentPrepared(ConfigurableEnvironment environment) { 17 //所有被添加到initialMulticaster中的listener都將監聽ApplicationEnvironmentPreparedEvent事件 18 this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent( 19 this.application, this.args, environment)); 20 } 21 22 }
4.1 根據properties和profiles配置環境:configureEnvironment(environment, applicationArguments.getSourceArgs());
以下假設指定配文件application-dev.properties,跟蹤一下源碼,可以發現:
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) { configurePropertySources(environment, args); configureProfiles(environment, args); }
configureEnvironment方法內部比較簡潔,直接調用兩個方法完事,
configurePropertySources(environment, args)方法的作用是將args封裝成了SimpleCommandLinePropertySource並加入到了environment中,其中arg中含有啟動參數:--spring.profiles.active=dev
configureProfiles(environment, args)作用是將啟動參數中指定的配置文件激活。
configureProfiles中執行enviroment.getActiveProfiles():強制讀取啟動命令中指定的配置文件
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; } }
5. 根據environment類型創建ApplicationContext,通常情況下,我們啟動的是一個Servlet應用,debug進createApplicationContext()源碼,可以看到內部初始化了AnnotationConfigServletWebServerApplicationContext類,也就是我們的上下文context
6. 上下文相關預處理,prepareContext()方法
1 private void prepareContext(ConfigurableApplicationContext context, 2 ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, 3 ApplicationArguments applicationArguments, Banner printedBanner) { 4 context.setEnvironment(environment); //設置容器環境,environment前面已經准備好 5 //配置beanNameGenerator和資源加載器 6 postProcessApplicationContext(context); 7 //回調所有的ApplicationContextInitializer初始化器 8 applyInitializers(context); 9 //發布容器以准備好的事件:執行所有SpringApplicationRunListener的contextPrepared方法,觸發事件,實際上EventPublishingRunListener中contextPrepared是一個空方法,什么都沒執行 10 listeners.contextPrepared(context); 11 if (this.logStartupInfo) { 12 logStartupInfo(context.getParent() == null); 13 logStartupProfileInfo(context); 14 } 15 16 //向Spring容器注入springApplicationArguments和springBootBanner,實際執行是將main函數的參數args和printedBanner分別封裝成單例bean注冊到容器中。 17 // Add boot specific singleton beans 18 context.getBeanFactory().registerSingleton("springApplicationArguments", 19 applicationArguments); 20 if (printedBanner != null) { 21 context.getBeanFactory().registerSingleton("springBootBanner", printedBanner); 22 } 23 24 // Load the sources 25 Set<Object> sources = getAllSources(); 26 Assert.notEmpty(sources, "Sources must not be empty");
//加載啟動類,將啟動類也注入容器 27 load(context, sources.toArray(new Object[0])); 28 //發布容器已加載事件:執行所有SpringApplicationRunListener的contextLoaded方法,下面是EventPublishingRunListener中的contextLoaded 29 listeners.contextLoaded(context); 30 }
具體load方法實現, load方法的作用其注釋寫的也很清楚,"Load beans into the application context",實際執行時可以發現主要是將我們的SpringBoot應用的啟動類(此處SpringbootdemoApplication2)注冊到容器中!前面if條件都為false,不執行,具體分析loader.loader()。
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(); }
loader.load()源碼:
public int load() { int count = 0; for (Object source : this.sources) { count += load(source); } return count; } private int load(Object source) { Assert.notNull(source, "Source must not be null"); if (source instanceof Class<?>) { return load((Class<?>) source); //執行 } if (source instanceof Resource) { return load((Resource) source); } if (source instanceof Package) { return load((Package) source); } if (source instanceof CharSequence) { return load((CharSequence) source); } throw new IllegalArgumentException("Invalid source type " + source.getClass()); } private int load(Class<?> source) { 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); } if (isComponent(source)) { this.annotatedReader.register(source); return 1; } return 0; }
isComponent(source)方法判斷當前類是不是有@Component注解,顯然啟動類的@SpringBootApplication這個組合注解是包含Component注解的。
this.annotatedReader.register(source)方法內部調用Spring框架底層提供的AnnotatedBeanDefinitionReader.doRegisterBean(xxx)方法,最終將SpringBoot應用的啟動類注冊到容器中。
7. 執行context的refresh,並且調用context的registerShutdownHook方法,refresh方法的具體邏輯分析可以參考:
Spring源碼解析 – AnnotationConfigApplicationContext容器創建過程
8. afterRefresh空方法
9. 執行所有runlisteners的started方法,發布ApplicationStartedEvent事件
10. 遍歷執行CommandLineRunner和ApplicationRunner
以上。