今天来聊一聊 springboot应用启动过程中的一些神操作
众所周知,springboot主启动类在启动的时候会调用如下代码
@SpringBootApplication
public class EmbeddedContainerApplication {
public static void main(String[] args) {
SpringApplication.run(EmbeddedContainerApplication.class, args);
}
}
那么这个 SpringApplication.run(EmbeddedContainerApplication.class, args); 底层下面究竟做了多少骚操作呢?
跟踪起源码:
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
return new SpringApplication(sources).run(args);
}
我们发现:主启动类的启动代码就做了两件事
第一件:new SpringApplication()创建了一个springApplication对象
第二件:执行run方法,开启应用
本篇文章来聊一聊在new SpringApplication()中究竟干了什么?
代码是基于springboot1.5.10版本的
在SpringApplication的构造方法中执行了以下代码
private final Set<Object> sources = new LinkedHashSet<Object>();
private boolean webEnvironment;
private List<ApplicationContextInitializer<?>> initializers;
private List<ApplicationListener<?>> listeners;
private Class<?> mainApplicationClass;
public SpringApplication(Object... sources) {
initialize(sources);
}
//在initialize方法中初始化写属性
private void initialize(Object[] sources) {
if (sources != null && sources.length > 0) {
//sources中存放的是存放的是传递进来的主启动类的class
this.sources.addAll(Arrays.asList(sources));
}
this.webEnvironment = deduceWebEnvironment();
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
1.设置主启动类 sources属性
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
2.设置当前是否是web环境
只要这两个类有一个不存在,就是 非web环境
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
private boolean deduceWebEnvironment() {
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return false;
}
}
return true;
}
3.设置应用的初始化器ApplicationContextInitializer
这一步是重点 ,这里面涉及到spring通过spi的方式来加载需要的类,需要反复阅读其中的源码,体会理解其中需要的含义
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//getSpringFactoriesInstances是如何加载所有的ApplicationContextInitializer的类型的呢?
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
private <T> Collection<? extends 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<String>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
SpringFactoriesLoader.loadFactoryNames(),见名知意,就是要加载names ,那么要加载那些names呢:根据注释可以得知,要加载某个类型的全类名(根据给定的接口或者抽象类型 加载所有的这个类型的实现类全类名)
/**
* Load the fully qualified class names of factory implementations of the
* given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
* class loader.
* @param factoryClass the interface or abstract class representing the factory
* @param classLoader the ClassLoader to use for loading resources; can be
* {@code null} to use the default
* @see #loadFactories
* @throws IllegalArgumentException if an error occurs while loading factory names
*/
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
try {
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List<String> result = new ArrayList<String>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
"] 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的资源
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
META-INF/spring.factories中存在的内容是
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
//将每个文件的key value 加载成properties对象
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
//从properties中取出org.springframework.context.ApplicationContextInitializer这个类型的
String factoryClassNames = properties.getProperty(factoryClassName);
//将取出的结果用逗号分割,放入result中返回
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
接下来是createSpringFactoriesInstances方法 ,因为类名都收集到了,接下来就通过反射机制来实例化这些类
private <T> List<T> createSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
Set<String> names) {
//names中存放的就是所有相关的类 全类名
List<T> instances = new ArrayList<T>(names.size());
for (String name : names) {
try {
//遍历这些类进行加载
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
//断言是不是ApplicationContextInitializer这个类型的
Assert.isAssignable(type, instanceClass);
//反射获取构造器
Constructor<?> constructor = instanceClass
.getDeclaredConstructor(parameterTypes);
//实例化对象
T instance = (T) BeanUtils.instantiateClass(constructor, args);
//实例化的对象保存在list中返回
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException(
"Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}
最后就是将这些ApplicationContextInitializer的对象集合,设置为SpringApplication对象的属性共使用
private List<ApplicationContextInitializer<?>> initializers;
public void setInitializers(
Collection<? extends ApplicationContextInitializer<?>> initializers) {
this.initializers = new ArrayList<ApplicationContextInitializer<?>>();
this.initializers.addAll(initializers);
}
到此,ApplicationContextInitializer已经初始化完毕,至于这个初始化器到底是干什么用的,我们留点悬念,下回分享
4.设置应用的监听器
同样的方式和ApplicationContextInitializer
private List<ApplicationListener<?>> listeners;
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
this.listeners = new ArrayList<ApplicationListener<?>>();
this.listeners.addAll(listeners);
}
也是利用spring的工厂加载机制 将所有META-INF/spring.factories文件中的
ApplicationListener类型的类加载进来进行实例化
然后设置到SpringApplication对象的属性中 供后续使用
5.推断主应用类
如何对端谁是主应用类呢
private Class<?> mainApplicationClass;
this.mainApplicationClass = deduceMainApplicationClass();
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;
}
核心是获取到方法调用栈的内容,如图
判断 调用栈中的含有main的方法,加载对应的类最后判定出启动类 是主应用类
本章总结 必看:
1.本文主要讲解了springboot启动的第一步 ,在第一步中创建了SpringApplication对象,并且对其进行了初始化,就是为它的一些属性赋值,包括ApplicationContextInitializer,ApplicationListener等
其中ApplicationContextInitializer,ApplicationListener监听器的作用。我们会专门挑一个章节来详细说明,贪多嚼不烂。
2.SpringFactoriesLoader是这部分内容的章节,负责加载所有META-INF/spring.factories文件中的内容,里面的代码可以详细的学习下
看这个类提供的javadoc就知道其作用
/**
*spring框架底层内部 一个通用的工厂加载机制
* General purpose factory loading mechanism for internal use within the framework.
*
* SpringFactoriesLoader这个类通过loadFactories这个方法,根据给定的类型来加载所有的类,这些
* 类型来自jar包中的META-INF/spring.factories文件,并且这个文件的格式必须是properties内容的
* 格式,key是全类名 value是逗号分割的实现类
* 例如 example.MyService=example.MyServiceImpl1,example.MyServiceImpl2
* 其中example.MyService是接口 后面那两个类是实现
* <p>{@code SpringFactoriesLoader} {@linkplain #loadFactories loads} and instantiates
* factories of a given type from {@value #FACTORIES_RESOURCE_LOCATION} files which
* may be present in multiple JAR files in the classpath. The {@code spring.factories}
* file must be in {@link Properties} format, where the key is the fully qualified
* name of the interface or abstract class, and the value is a comma-separated list of
* implementation class names. For example:
*
* <pre class="code">example.MyService=example.MyServiceImpl1,example.MyServiceImpl2</pre>
*
* where {@code example.MyService} is the name of the interface, and {@code MyServiceImpl1}
* and {@code MyServiceImpl2} are two implementations.
public abstract class SpringFactoriesLoader {
/**
* 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";
}
3.稍微的需要了解下类的加载机制
ClassLoader.loadClass(className)和Class.forName(className)了解下加载机制