1. 程式人生 > >SpringBoot原始碼---啟動流程分析

SpringBoot原始碼---啟動流程分析

 既然看到這篇文章了,那麼預設讀者已經很熟悉SpringBoot的使用的。

 第一步,啟動一個SpringBoot應用:

@ComponentScan(basePackages = {""})
@MapperScan("")
@SpringBootApplication
public class StartApp {
    public static void main(String[] args) {
        SpringApplicationBuilder builder = new SpringApplicationBuilder(StartApp.class);
        // 修改Banner的模式為OFF
        builder.bannerMode(Banner.Mode.LOG).run(args);
        //載入系統配置項
    }
}

 或者呢,可以這樣啟動:

@ComponentScan(basePackages = {""})
@MapperScan("")
@SpringBootApplication
public class StartApp {
    public static void main(String[] args) {
       SpringApplication sa = new SpringApplication(StartApp.class);
       sa.run();
    }
}

    兩者是等價的,但是推薦使用第一種。為什麼呢?因為SpringApplicationBuilder對SpringApplication做了一些封裝,我們可以按需要設定更多定製化配置項。

第二步,SpringApplicationBuilder的例項化

public SpringApplicationBuilder(Class<?>... sources) {
	this.application = createSpringApplication(sources);
}

protected SpringApplication createSpringApplication(Class<?>... sources) {
	return new SpringApplication(sources);
}

   以上就是在例項化一個SpringApplicationBuilder類時它為我們做的事情。可以發現,它間接例項化了一個SpringApplication物件,這就是它們為什麼等價的原因。好了,這些當然不是這篇文章的主要任務。我們接著往下看。

第三步,SpringApplication物件的例項化

public SpringApplication(Class<?>... primarySources) {
	this(null, primarySources);
}

@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        //resourceLoader的設定
	this.resourceLoader = resourceLoader;
        //斷言保證primarySources不能為空,也就是例項化SpringApplication時的引數不能為空
	Assert.notNull(primarySources, "PrimarySources must not be null");
        //把primarySources陣列轉換為set集合
	this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        //判斷webApplicationType,也就是當前Springboot應用型別
	this.webApplicationType = deduceWebApplicationType();
        //收集ApplicationContextInitializer事件類
	setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
       //收集ApplicationListener事件類
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
}

 在上述方法中,忽略一眼就能看明白的步驟,我們可以看到主要做了一些蒐集ApplicationContextInitializer和ApplicationListener類的工作。根據getSpringFactoriesInstances()方法的名稱,我們簡單判斷是一個從Spring容器中獲取指定型別物件的方法。那麼,具體看看它的實現:

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 = Thread.currentThread().getContextClassLoader();
	// Use names and ensure unique to protect against duplicates
	Set<String> names = new LinkedHashSet<>(
        //抽象出配置檔案中所有指定型別的Factory的名稱集合
		SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        //建立Spring Factory類的例項集合
	List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
		classLoader, args, names);
        //排序
	AnnotationAwareOrderComparator.sort(instances);
	return instances;
}

 非常有意思的是SpringFactoriesLoader.loadFactoryNames(type, classLoader))方法這一步做的事情,它會把META-INF/spring.factories檔案中的所有Factory的名稱都掃描出來並放到集合中,程式碼如下:

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
	String factoryClassName = factoryClass.getName();
	return loadSpringFactories(classLoader).getOrDefault(factoryClassName, 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 {
                //如果當前Springboot所線上程的classLoader 不為空,就從classLoader中獲取META-INF/spring.factories檔案;
                //否則,從系統盤中獲取(針對不同執行環境)
		Enumeration<URL> urls = (classLoader != null ?
			classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
			ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION))	;
		result = new LinkedMultiValueMap<>();
		while (urls.hasMoreElements()) {
			URL url = urls.nextElement();
            //把
			UrlResource resource = new UrlResource(url);
			Properties properties = PropertiesLoaderUtils.loadProperties(resource);
			for (Map.Entry<?, ?> entry : properties.entrySet()) {
					List<String> factoryClassNames = Arrays.asList(
					StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
				result.addAll((String) entry.getKey(), factoryClassNames);
			}
		}
		cache.put(classLoader, result);
		return result;
	}
	catch (IOException ex) {
			
	}
}

到這裡,一些屬性設定相關的操作就完成了。當然,SpringApplicationBuilder和SpringApplication都支援很多個性化配置。我們可以自己去設定相關內容。

第四步,SpringApplicationBuilder的啟動run()方法。

public ConfigurableApplicationContext run(String... args) {
    //如果已經啟動就直接返回當前context並停止後續操作
	if (this.running.get()) {
		// If already created we just return the existing context
		return this.context;
	}
    //如果父容器不為空,那麼標記當前應用為子容器並啟動父容器(Maven多模組開發中會用到)
	configureAsChildIfNecessary(args);
    //如果當前應用沒有啟動就啟動它
	if (this.running.compareAndSet(false, true)) {
		synchronized (this.running) {
			// If not already running copy the sources over and then run.
			this.context = build().run(args);
		}
	}
	return this.context;
}

 可以看到,它呼叫了一個run()方法,由SpringApplication來實現的:

public ConfigurableApplicationContext run(String... args) {
	StopWatch stopWatch = new StopWatch();
	stopWatch.start();
	ConfigurableApplicationContext context = null;
	Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
	configureHeadlessProperty();
        //搜尋得到所有的啟動時ApplicationListener事件監聽類
	SpringApplicationRunListeners listeners = getRunListeners(args);
        //啟動所有的啟動時ApplicationListener事件監聽類
	listeners.starting();
	try {
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
		ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
		configureIgnoreBeanInfo(environment);
                //列印Banner條
		Banner printedBanner = printBanner(environment);
	        context = createApplicationContext();
		exceptionReporters = getSpringFactoriesInstances(
				SpringBootExceptionReporter.class,
				new Class[] { ConfigurableApplicationContext.class }, context);
		repareContext(context, environment, listeners, applicationArguments,
					printedBanner);
		refreshContext(context);
		afterRefresh(context, applicationArguments);
		stopWatch.stop();
		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;
}

需要重點關注refreshContext(context)方法,它的實現如下:

private void refreshContext(ConfigurableApplicationContext context) {
	refresh(context);
	if (this.registerShutdownHook) {
		try {
			context.registerShutdownHook();
		}
		catch (AccessControlException ex) {
				// Not allowed in some environments.
		}
	}
}

好了,終於要放大招了。看到refresh(context)方法沒?它的實現在AbstractApplicationContext類中。這個類不用多說,你懂得^>^。