SpringBoot系列—Run啓動原理(十八)

個人博客:haichenyi.com。感謝關注

  需要搞清楚幾個重要的事件回調機制

配置在META-INF/spring.factories

  • ApplicationContextInitializer
  • SpringApplicationRunListener

只需要放在ioc容器中

  • ApplicationRunner

  • CommandLineRunner

  新建一個空項目,就勾選web,找到啓動類,每個方法上面寫的註釋,可以看一下:

@SpringBootApplication
public class SellApplication {

    public static void main(String[] args) {
        SpringApplication.run(SellApplication.class, args);
    }

}

//上面run方法點進來
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
		return run(new Class<?>[] { primarySource }, args);
	}

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return new SpringApplication(primarySources).run(args);
	}
	

看到這裏,就應該看到了,啓動流程分爲兩步

  1. 創建SpringApplication對象
  2. 運行run方法

創建SpringApplication對象

//上面的構造方法點進去
//這裏與1.5版本不一樣的地方就是,
//2.X這裏調用了重載的構造方法,而1.5這裏調用的是一個initialize()方法,這個方法裏面的內容,與下面兩個參數的重載方法差不多
public SpringApplication(Class<?>... primarySources) {
		this(null, primarySources);
	}

//下面這個就是this調用的重載的構造方法
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		//保存主配置類,1.5裏面這裏有個非空判斷,用if做的,這裏換成的斷言做判斷
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		//判斷當前是否一個web應用
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		//從類路徑下找到META‐INF/spring.factories配置的所有ApplicationContextInitializer;然後保存起 來
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		//從類路徑下找到ETA‐INF/spring.factories配置的所有ApplicationListener
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		//從多個配置類中找到有main方法的主配置類
		this.mainApplicationClass = deduceMainApplicationClass();
	}

setInitializers()方法

  看方法名就知道,這個是初始化方法,初始化什麼東西呢?再看傳的參數ApplicationContextInitializer,就是一開始我們提到的類。我們看這個是怎麼獲取的

//第一步:
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) {
		ClassLoader classLoader = getClassLoader();
		// Use names and ensure unique to protect against duplicates
		//看這裏的導入方法SpringFactoriesLoader.loadFactoryNames(type, classLoader)
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}
	
//第三步:
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
	}

//第四步,就是這裏了。看這裏面的實現
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
		//一眼看過去。很明顯,這裏就是classLoader.getResources(),導入的本地的資源。看這個傳的參數,我放到這個方法下面去了
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			//這裏通過一個while循環,加載本地配置的ApplicationContextInitializer
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}
	
/**
 * The location to look for factories.
 * <p>Can be present in multiple JAR files.
 */
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

  所以,從上面的源碼,我們一步一步點擊進去看,我們就能發現,他最終都是加載到 META-INF/spring.factories 目錄下的 ApplicationContextInitializer 當然,到目前爲止這裏只是初始化

setListeners()方法

  一眼就能看出來,這裏是設置監聽方法

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

  一眼看過去,這個setListener方法傳的參數熟不熟悉?就是我們上面初始化的時候傳的參數是同一個方法。所以,這裏設置監聽設置哪些監聽方法也是META-INF/spring.factories 目錄下的listener方法,我們看一下這個文件內容:

spring_factories.png

  這些都是是自動配置類的內容

運行Run方法

public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
		//獲取SpringApplicationRunListeners;從類路徑下META‐INF/spring.factories
		SpringApplicationRunListeners listeners = getRunListeners(args);
		//回調所有的獲取SpringApplicationRunListener.starting()方法
		listeners.starting();
		try {
		    //封裝命令行參數
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			//準備環境,創建環境完成後回調SpringApplicationRunListener.environmentPrepared();表示環境準 備完成
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			//這裏是新增的,點擊去看,就是再properties文件中配置你需要忽略的bean
			configureIgnoreBeanInfo(environment);
			//這個是打印spring的logo banner圖
			Banner printedBanner = printBanner(environment);
			/創建ApplicationContext;這個下面有下介紹
			context = createApplicationContext();
			//看一下參數,這個就是做異常報告處理的
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
					
			//準備上下文環境;將environment保存到ioc中;而且applyInitializers();
			//applyInitializers():回調之前保存的所有的ApplicationContextInitializer的initialize方法
			//回調所有的SpringApplicationRunListener的contextPrepared();	
			//prepareContext運行完成以後回調所有的SpringApplicationRunListener的contextLoaded();
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
			//s刷新容器;ioc容器初始化(如果是web應用還會創建嵌入式的Tomcat);Spring註解版
			//掃描,創建,加載所有組件的地方;(配置類,組件,自動配置)
			refreshContext(context);
			//2.x裏面是空方法
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);
			
			//從ioc容器中獲取所有的ApplicationRunner和CommandLineRunner進行回調 //ApplicationRunner先回調,CommandLineRunner再回調
			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);
		}
		//返回這個IOC容器
		return context;
	}

getRunListeners()方法

  我們看到上面第一個有註釋的位置:getRunListeners方法

private SpringApplicationRunListeners getRunListeners(String[] args) {
		Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
		return new SpringApplicationRunListeners(logger,
				getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}

  方法熟悉嗎?就是我們上面初始化的時候調用的那個方法,只是這裏的參數傳的是:SpringApplicationRunListener,我們看最開始說的,這就是我們要了解的第二個內容,回調都是怎麼調用的

  方法裏面的註釋也寫了,先獲取監聽事件,然後回調starting方法,我們看一下這個接口有那些回調方法:

public interface SpringApplicationRunListener {

	default void starting() {
	}
	
	default void environmentPrepared(ConfigurableEnvironment environment) {
	}
	
	default void contextPrepared(ConfigurableApplicationContext context) {
	}

	default void contextLoaded(ConfigurableApplicationContext context) {
	}

	default void started(ConfigurableApplicationContext context) {
	}

	default void running(ConfigurableApplicationContext context) {
	}

	default void failed(ConfigurableApplicationContext context, Throwable exception) {
	}

}

  就這些回調,這裏還用了1.8的新特性,default關鍵字,接口裏面的方法可以有方法體

prepareEnvironment()

  看到第二個寫註釋的位置,眼熟嗎?可不就是跟上面回調方法名字相同麼?我們點進去看一下

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		// Create and configure the environment
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		ConfigurationPropertySources.attach(environment);
		
		//這裏劃重點,這裏就調用的environmentPrepared的回調方法
		listeners.environmentPrepared(environment);
		bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
			environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
					deduceEnvironmentClass());
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

  準備完環境之後,調用environmentPrepared的回調

createApplicationContext()

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);
	}

  創建applicationContext,這裏跟1.5不一樣,1.5就只有兩種:一種是web的ioc容器,一種是默認的ioc容器。2.X這裏有三種:DEFAULT_CONTEXT_CLASS,DEFAULT_SERVLET_WEB_CONTEXT_CLASS,DEFAULT_REACTIVE_WEB_CONTEXT_CLASS,實際字符串比較長,可以去看一下源碼。然後用BeanUtils通過反射創建。

prepareContext()方法

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
			SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
		//將environment放到context中
		context.setEnvironment(environment);
		postProcessApplicationContext(context);
		//初始化
		applyInitializers(context);
		//這裏回調contextPrepared方法
		listeners.contextPrepared(context);
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		// Add boot specific singleton beans
		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]));
		//這裏回調contextLoaded()方法
		listeners.contextLoaded(context);
	}

  準備上下文環境;將environment保存到ioc中;而且applyInitializers()

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);
		}
	}

  這裏就將我們一開始從配置文件裏面讀取,然後創建ApplicationContextInitializer初始化。

  當環境準備好之後,就回調了SpringApplicationRunListener的contextPrepared();

  當所有的都準備好了之後,回調SpringApplicationRunListener的contextLoaded();

  到這裏,所有的環境都準備好了,需要打印的logo也加進去了。

refreshContext()

  刷新容器這個方法,我們可以點到具體的功能實現裏面,可以看到,這裏就是掃描,創建,加載所有的組件,配置類,組件,自動配置等。

  到這裏,這個方法創建完之後,所有的控制器就創建完了,所有的組件,bean等,都在控制檯打印出來了。如果是web應用,還會創建嵌入式的tomcat。我們spring boot項目內嵌tomcat,就是在這裏創建的。

afterRefresh()

  1.5版本這個方法裏面回調的是callRunners方法,而2.X版本,現在這是個空方法裏面並沒有實現。callRunners被提出來了,放到了最後面。

started(),running()

  在1.5版本這裏,也就是afterRefresh()之後,應該是調用的SpringApplicationRunListeners的finished()方法。

  在2.X版本之後,去掉了finished方法,改成了調用started方法,然後調用running方法。我們上面有一個starting方法,從這裏名字就可以看出來,相當於,首先是正在啓動當中,然後就是啓動完成了,正在運行了。

callRunners()

private void callRunners(ApplicationContext context, ApplicationArguments args) {
		List<Object> runners = new ArrayList<>();
		runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
		runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
		AnnotationAwareOrderComparator.sort(runners);
		for (Object runner : new LinkedHashSet<>(runners)) {
			if (runner instanceof ApplicationRunner) {
				callRunner((ApplicationRunner) runner, args);
			}
			if (runner instanceof CommandLineRunner) {
				callRunner((CommandLineRunner) runner, args);
			}
		}
	}

  從IOC容器中(不是配置文件)獲取所有的的ApplicationRunner和CommandLineRunner進行回調。這也是最開始說的兩個注意的地方

  並且,這裏有個先後順序,先回調的ApplicationRunner,後回調的CommandLineRunner

  這裏也是run方法,最後執行的地方。從這裏就是真正的開啓了run。

  最後一步,返回那個context就是返回IOC容器對象。到這裏,我們的spring boot就啓動完成了。

  這就是我們的spring boot的啓動原理。初始化,listener的回調,Runner的回調都說的很清楚。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章