springboot源码分析:初始化流程

Scroll Down

道阻且长,行则将至。
入口代码如下:

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

初始化流程的入口就是创建SpringApplication的对象:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
	this.resourceLoader = resourceLoader;
	Assert.notNull(primarySources, "PrimarySources must not be null");
	this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
	this.webApplicationType = WebApplicationType.deduceFromClasspath();
	setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	this.mainApplicationClass = deduceMainApplicationClass();
}

(1)、设置resourceLoader及primarySources。
(2)、需要确定WebApplicationType,
该类型共有三个实例

/**
 * The application should not run as a web application and should not start an
 * embedded web server.
 */
NONE,

/**
 * The application should run as a servlet-based web application and should start an
 * embedded servlet web server.
 */
SERVLET,

/**
 * The application should run as a reactive web application and should start an
 * embedded reactive web server.
 */
REACTIVE;

决定使用哪个类型,是通过:判断classPath下是否存在javax.servlet.Servlet,如果不存在则返回NONE,如果存在org.springframework.web.servlet.DispatcherServlet那么就使用SERVLET。
而判断是否存在该类型则是通过如下代码实现的:主要是使用forName

public static Class<?> forName(String name, @Nullable ClassLoader classLoader)
			throws ClassNotFoundException, LinkageError {

	Assert.notNull(name, "Name must not be null");

	Class<?> clazz = resolvePrimitiveClassName(name);
	if (clazz == null) {
		clazz = commonClassCache.get(name);
	}
	if (clazz != null) {
		return clazz;
	}

	// "java.lang.String[]" style arrays
	if (name.endsWith(ARRAY_SUFFIX)) {
		String elementClassName = name.substring(0, name.length() - ARRAY_SUFFIX.length());
		Class<?> elementClass = forName(elementClassName, classLoader);
		return Array.newInstance(elementClass, 0).getClass();
	}

	// "[Ljava.lang.String;" style arrays
	if (name.startsWith(NON_PRIMITIVE_ARRAY_PREFIX) && name.endsWith(";")) {
		String elementName = name.substring(NON_PRIMITIVE_ARRAY_PREFIX.length(), name.length() - 1);
		Class<?> elementClass = forName(elementName, classLoader);
		return Array.newInstance(elementClass, 0).getClass();
	}

	// "[[I" or "[[Ljava.lang.String;" style arrays
	if (name.startsWith(INTERNAL_ARRAY_PREFIX)) {
		String elementName = name.substring(INTERNAL_ARRAY_PREFIX.length());
		Class<?> elementClass = forName(elementName, classLoader);
		return Array.newInstance(elementClass, 0).getClass();
	}

	ClassLoader clToUse = classLoader;
	if (clToUse == null) {
		clToUse = getDefaultClassLoader();
	}
	try {
		return Class.forName(name, false, clToUse);
	}
	catch (ClassNotFoundException ex) {
		int lastDotIndex = name.lastIndexOf(PACKAGE_SEPARATOR);
		if (lastDotIndex != -1) {
			String innerClassName =
						name.substring(0, lastDotIndex) + INNER_CLASS_SEPARATOR + name.substring(lastDotIndex + 1);
			try {
				return Class.forName(innerClassName, false, clToUse);
			}
			catch (ClassNotFoundException ex2) {
				// Swallow - let original exception get through
			}
		}
		throw ex;
	}
}

ps:以上代码是ClassUtils类。该类作为工具类,在class标记的时候使用了abstract,禁止被实例化。
最终还是使用了Class.forName来尝试加载类,如果返回为空,那么就是这个类找不到。这里还会尝试加载内部类,如果内部类也加载不到那么再抛出ClassNotFoundException。
此时如果Springboot中内置了DispatcherServlet的话,WebApplicationType就是SERVLET。
** (3)、设置ApplicationContextInitializer和ApplicationListener到SpringApplication中:
具体的操作是依赖下面的代码来完成的。

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
	ClassLoader classLoader = getClassLoader();
	// Use names and ensure unique to protect against duplicates
	Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
	List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
	AnnotationAwareOrderComparator.sort(instances);
	return instances;
}

首先获取classPath下面的所有的指定类型的类名,然后根据类名创建实例,并返回,设置到SpringApplication中。这里主要是依赖SpringFactoriesLoader类实现的。该类也是个工具类,被声明为final。构造函数被声明为private,禁止实例化。

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
	MultiValueMap<String, String> result = cache.get(classLoader);
	if (result != null) {
		return result;
	}

	try {
		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()) {
				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);
	}
}

此处使用的是java的SPI机制。此处默认使用的ClassLoader是当前线程的:

@Nullable
public static ClassLoader getDefaultClassLoader() {
	ClassLoader cl = null;
	try {
		cl = Thread.currentThread().getContextClassLoader();
	}
	catch (Throwable ex) {
		// Cannot access thread context ClassLoader - falling back...
	}
	if (cl == null) {
		// No thread context class loader -> use class loader of this class.
		cl = ClassUtils.class.getClassLoader();
		if (cl == null) {
			// getClassLoader() returning null indicates the bootstrap ClassLoader
			try {
				cl = ClassLoader.getSystemClassLoader();
			}
			catch (Throwable ex) {
				// Cannot access system ClassLoader - oh well, maybe the caller can live with null...
			}
		}
	}
	return cl;
}

通过classLoader来获取路径下面的资源。此处不再详细描述。
找到所有定义在META-INF/spring.factories中的ApplicationContextInitializer和ApplicationListener实现类。并创建实例,设置到SpringApplication中。此处使用了BeanUtils和ReflectionUtils来实例化对象。
Springboot默认加载的ApplicationContextInitializer和ApplicationListener的实现类如下:

org.springframework.boot.ClearCachesApplicationListener,\清理通过反射获取的Method和Field时存储的缓存
org.springframework.boot.builder.ParentContextCloserApplicationListener,\监听父容器关闭事件
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\该类还是个EnvironmentPostProcessor,处理VCAP。
org.springframework.boot.context.FileEncodingApplicationListener,\判断系统的文件编码是否与spring.mandatory-file-encoding一致,不一致会报错。
org.springframework.boot.context.config.AnsiOutputApplicationListener,\忽略
org.springframework.boot.context.config.ConfigFileApplicationListener,\加载application.properties或者其他文件后面详细介绍
org.springframework.boot.context.config.DelegatingApplicationListener,\通过该类将已经加载的listener注册到SimpleApplicationEventMulticaster中
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\通知检查完成的listener
org.springframework.boot.context.logging.LoggingApplicationListener,\配置logging类
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener忽略
org.springframework.boot.autoconfigure.BackgroundPreinitializer忽略

ApplicationContextInitializer的实现类如下:

org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener,
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

后会面会重点介绍ConfigFileApplicationListener

** (4)、推断Main方法所在的类:
这里面做的操作就是抛出一个异常,然后通过异常栈来判断main方法所在的类。

private Class<?> deduceMainApplicationClass() {
	try {
		StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
		for (StackTraceElement stackTraceElement : stackTrace) {
			if ("main".equals(stackTraceElement.getMethodName())) {
				return Class.forName(stackTraceElement.getClassName());
			}
		}
	}
	catch (ClassNotFoundException ex) {
		// Swallow and continue
	}
	return null;
}

至此,SpringApplication就初始化完成。