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类中。这个类不用多说,你懂得^>^。

猜你喜欢

转载自blog.csdn.net/qq_28802119/article/details/83268240