您好,登錄后才能下訂單哦!
這篇“SpringBoot啟動流程SpringApplication源碼分析”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“SpringBoot啟動流程SpringApplication源碼分析”文章吧。
SpringBoot啟動流程源碼分析一、入口參數研究和創建對象
以下先看下SpringApplication的run()方法
package org.springframework.boot; public ConfigurableApplicationContext run(String... args) { //1.計時器 StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); //2.headless配置 configureHeadlessProperty(); //3、獲取監聽 SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { //應用程序啟動的參數 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); //4、準備環境 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); //環境創建成功后,配置bean信息,決定是否跳過 BeanInfo 類的掃描,如果設置為 true,則跳過 configureIgnoreBeanInfo(environment); //打印banner信息 Banner printedBanner = printBanner(environment); context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments); //停止計時 stopWatch.stop(); //控制是否打印日志的,這里為true,即打印日志 if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); } 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; }
我將會根據執行過程逐行進行分析
此類實則為計時器,如下對具體使用進行分析
StopWatch stopWatch = new StopWatch(); //開始計時 stopWatch.start(); //停止計時 stopWatch.stop();
對于具體打印的上面寫的為
//將當前類傳入StartupInfoLogger創建了一個對象 //然后調用logStarted打印日志 new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); //創建一個Log類 protected Log getApplicationLog() { if (this.mainApplicationClass == null) { return logger; } return LogFactory.getLog(this.mainApplicationClass); } //調用log類的log.info()方法來打印日志 public void logStarted(Log log, StopWatch stopWatch) { if (log.isInfoEnabled()) { log.info(getStartedMessage(stopWatch)); } } //打印詳細的日志 private StringBuilder getStartedMessage(StopWatch stopWatch) { StringBuilder message = new StringBuilder(); message.append("Started "); message.append(getApplicationName()); message.append(" in "); message.append(stopWatch.getTotalTimeSeconds()); try { double uptime = ManagementFactory.getRuntimeMXBean().getUptime() / 1000.0; message.append(" seconds (JVM running for " + uptime + ")"); } catch (Throwable ex) { // No JVM time available } return message; }
這里可以看到stopWatch.getTotalTimeSeconds()方法就是來獲取實際的計時時間的。再者,通過這幾行代碼,我們也可以考慮下平常在寫代碼的時候,有幾種日志打印方式?SpringBoot是怎么集成日志框架的?
private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless"; private void configureHeadlessProperty() { System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless))); }
這一部分代碼這樣理解吧,首先java.awt包提供了用于創建用戶界面和繪制圖形圖像的所有分類,那么 屬性SYSTEM_PROPERTY_JAVA_AWT_HEADLESS就一定會和用戶界面相關了。 這里將SYSTEM_PROPERTY_JAVA_AWT_HEADLESS設置為true,其實就是表示在缺少顯示屏、鍵盤或者鼠標中的系統配置,如果將其設置為true,那么headless工具包就會被使用。
總體上可以分這三步
獲取一個默認的加載器
根據類型獲取spring.factories中符合的類名
創建類實例,返回
如下將跟下代碼
//獲取所有監聽 SpringApplicationRunListeners listeners = getRunListeners(args); //啟動監聽 listeners.starting();
跳轉進入getRunListeners方法
private SpringApplicationRunListeners getRunListeners(String[] args) { Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class }; return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)); } private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { //3.1獲取類加載器 ClassLoader classLoader = getClassLoader(); // Use names and ensure unique to protect against duplicates //3.2 根據類型獲取spring.factories中符合的類名 Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); //3.3 創建類實例 List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); //對實例進行排序 AnnotationAwareOrderComparator.sort(instances); return instances; }
先看下SpringApplicationRunListeners類
/** * A collection of {@link SpringApplicationRunListener}. * * @author Phillip Webb */ 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); }
SpringApplicationRunListeners類內部關聯了SpringApplicationRunListener的集合,說白了就是用List集合存儲了SpringApplicationRunListeners類,那么,我們就需要了解一下這個類是干嘛的
老規矩,先把源碼抬上來
/** *//可以理解為Spring Boot應用的運行時監聽器 * Listener for the {@link SpringApplication} {@code run} method. *//SpringApplicationRunListener的構造器參數必須依次為SpringApplication和String[]類型 * {@link SpringApplicationRunListener}s are loaded via the {@link SpringFactoriesLoader} * and should declare a public constructor that accepts a {@link SpringApplication} * instance and a {@code String[]} of arguments. *//每次運行的時候將會創建一個 SpringApplicationRunListener A new * {@link SpringApplicationRunListener} instance will be created for each run. * */ public interface SpringApplicationRunListener { /** * Called immediately when the run method has first started. Can be used for very * early initialization. */ //Spring應用剛啟動 void starting(); /** * Called once the environment has been prepared, but before the * {@link ApplicationContext} has been created. * @param environment the environment */ //ConfigurableEnvironment準備妥當,允許將其調整 void environmentPrepared(ConfigurableEnvironment environment); /** * Called once the {@link ApplicationContext} has been created and prepared, but * before sources have been loaded. * @param context the application context */ //ConfigurableApplicationContext準備妥當,允許將其調整 void contextPrepared(ConfigurableApplicationContext context); /** * Called once the application context has been loaded but before it has been * refreshed. * @param context the application context */ //ConfigurableApplicationContext已裝載,但是任未啟動 void contextLoaded(ConfigurableApplicationContext context); /** * The context has been refreshed and the application has started but * {@link CommandLineRunner CommandLineRunners} and {@link ApplicationRunner * ApplicationRunners} have not been called. * @param context the application context. * @since 2.0.0 */ //ConfigurableApplicationContext已啟動,此時Spring Bean已初始化完成 void started(ConfigurableApplicationContext context); /** * Called immediately before the run method finishes, when the application context has * been refreshed and all {@link CommandLineRunner CommandLineRunners} and * {@link ApplicationRunner ApplicationRunners} have been called. * @param context the application context. * @since 2.0.0 */ //Spring應用正在運行 void running(ConfigurableApplicationContext context); /** * Called when a failure occurs when running the application. * @param context the application context or {@code null} if a failure occurred before * the context was created * @param exception the failure * @since 2.0.0 */ //Spring應用運行失敗 void failed(ConfigurableApplicationContext context, Throwable exception); }
單純的看源碼,是一個簡單的接口,這時候我們可以看下作者給的注釋。理解部分就直接加到上面源碼中了。
再看下他的實現類EventPublishingRunListener
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered { private final SpringApplication application; private final String[] args; private final SimpleApplicationEventMulticaster initialMulticaster; 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); } }
這里我們看到兩點:
構造器參數和他實現的接口(上面剛分析了)注釋中規定的一致
將SpringApplication中的ApplicationListener實例列表全部添加到了SimpleApplicationEventMulticaster對象中
SimpleApplicationEventMulticaster是Spring框架的一個監聽類,用于發布Spring應用事件。因此EventPublishingRunListener實際充當了Spring Boot事件發布者的角色。
這里我再跟進源碼的時候發現,針對SpringBoot的事件/監聽機制內容還是挺多的,我們在充分理解的時候需要先了解Spring的事件/監聽機制,后面將兩個結合后單獨進行對比分析。
ClassLoader classLoader = getClassLoader(); public ClassLoader getClassLoader() { if (this.resourceLoader != null) { return this.resourceLoader.getClassLoader(); } return ClassUtils.getDefaultClassLoader(); }
這里的類加載器獲取首先是獲取resourceLoader的類加載器,獲取不到則獲取默認的類加載器。 resourceLoader是資源加載器類,有具體的實現類。
SpringFactoriesLoader.loadFactoryNames(type, classLoader) public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) { //獲取類型名稱:org.springframework.context.ApplicationContextInitializer String factoryClassName = factoryClass.getName(); return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); }
我們繼續對loadSpringFactories追下去
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { //從緩存里面獲取 MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader); if (result != null) { return result; } else { try { //執行classLoader.getResources("META-INF/spring.factories"),表示通過加載器獲取META-INF/spring.factories下的資源 Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories"); LinkedMultiValueMap result = new LinkedMultiValueMap(); while(urls.hasMoreElements()) { //文件地址 URL url = (URL)urls.nextElement(); //從指定位置加載UrlResource UrlResource resource = new UrlResource(url); //加載里面的屬性,屬性見下圖 Properties properties = PropertiesLoaderUtils.loadProperties(resource); Iterator var6 = properties.entrySet().iterator(); while(var6.hasNext()) { Entry<?, ?> entry = (Entry)var6.next(); //獲取key值 String factoryClassName = ((String)entry.getKey()).trim(); //獲取value值 String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue()); int var10 = var9.length; //這里是將查詢出來的key作為result的key,value轉換成字符數組存放到result的value中 for(int var11 = 0; var11 < var10; ++var11) { String factoryName = var9[var11]; result.add(factoryClassName, factoryName.trim()); } } } //將結果集存入緩存中 cache.put(classLoader, result); return result; } catch (IOException var13) { throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13); } } }
default V getOrDefault(Object key, V defaultValue) { V v; return (((v = get(key)) != null) || containsKey(key)) ? v : defaultValue; }
這個的意思是如果沒有,則獲取一個空的list
這一步其實就是將上一步從META-INF/spring.factories加載進來的資源進行實例化。
private <T> List<T> createSpringFactoriesInstances()(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) { List<T> instances = new ArrayList<>(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; }
prepareEnvironment(listeners, applicationArguments)
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { // Create and configure the environment //4.1 創建一個環境 ConfigurableEnvironment environment = getOrCreateEnvironment(); //4.2 配置環境 configureEnvironment(environment, applicationArguments.getSourceArgs()); //4.3 ConfigurationPropertySourcesPropertySource對象存入到第一位 ConfigurationPropertySources.attach(environment); //listeners環境準備(就是廣播ApplicationEnvironmentPreparedEvent事件) listeners.environmentPrepared(environment); // 將環境綁定到SpringApplication bindToSpringApplication(environment); // 如果是非web環境,將環境轉換成StandardEnvironment if (!this.isCustomEnvironment) { environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass()); } // 配置PropertySources對它自己的遞歸依賴 ConfigurationPropertySources.attach(environment); return environment; }
private ConfigurableEnvironment getOrCreateEnvironment() { //有的話,直接返回 if (this.environment != null) { return this.environment; } //這里我們在上面見到過,通過WebApplicationType.deduceFromClasspath()方法獲取的 switch (this.webApplicationType) { case SERVLET: return new StandardServletEnvironment(); case REACTIVE: return new StandardReactiveWebEnvironment(); default: return new StandardEnvironment(); } }
這里創建了一個StandardServletEnvironment實例的環境 systemProperties用來封裝了JDK相關的信息 如下圖
systemEnvironment用來封轉環境相關的信息
封裝的還是挺詳細的哈。
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) { if (this.addConversionService) { ConversionService conversionService = ApplicationConversionService.getSharedInstance(); environment.setConversionService((ConfigurableConversionService) conversionService); } configurePropertySources(environment, args); configureProfiles(environment, args); }
setConversionService(ConfigurableConversionService conversionService)方法繼承于ConfigurablePropertyResolver接口, 該接口是PropertyResolver類型都將實現的配置接口。提供用于訪問和自定義將屬性值從一種類型轉換為另一種類型時使用的ConversionService的工具。PropertyResolver是用于針對任何底層源解析屬性的接口。
configurePropertySources(environment, args);當前方法主要是將啟動命令中的參數和run 方法中的參數封裝為PropertySource。
protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) { //獲取所有的屬性源,就是獲取4.1的ConfigurableEnvironment上獲取到的屬性 MutablePropertySources sources = environment.getPropertySources(); if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) { sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties)); } //是否添加命令啟動參數,addCommandLineProperties為true,表示需要添加,但是前提是你得配置了參數 if (this.addCommandLineProperties && args.length > 0) { String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME; if (sources.contains(name)) { PropertySource<?> source = sources.get(name); CompositePropertySource composite = new CompositePropertySource(name); composite.addPropertySource( new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args)); composite.addPropertySource(source); sources.replace(name, composite); } else { sources.addFirst(new SimpleCommandLinePropertySource(args)); } } }
configureProfiles(environment, args);環境配置
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<>(this.additionalProfiles); profiles.addAll(Arrays.asList(environment.getActiveProfiles())); //設置當前的環境 environment.setActiveProfiles(StringUtils.toStringArray(profiles)); }
public static void attach(Environment environment) { Assert.isInstanceOf(ConfigurableEnvironment.class, environment); //獲取所有的屬性源,就是獲取4.1的ConfigurableEnvironment上獲取到的屬性 MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources(); //判斷是否有 屬性 configurationProperties PropertySource<?> attached = sources.get(ATTACHED_PROPERTY_SOURCE_NAME); if (attached != null && attached.getSource() != sources) { sources.remove(ATTACHED_PROPERTY_SOURCE_NAME); attached = null; } if (attached == null) { // 將sources封裝成ConfigurationPropertySourcesPropertySource對象,并把這個對象放到sources的第一位置 sources.addFirst(new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME, new SpringConfigurationPropertySources(sources))); } }
以上就是關于“SpringBoot啟動流程SpringApplication源碼分析”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。