今天来聊一聊 springboot应用启动过程中的一些神操作
众所周知,springboot主启动类在启动的时候会调用如下代码
@SpringBootApplicationpublic 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中存放的是存放的是传递进来的主启动类的classthis.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中存在的内容是
# Initializersorg.springframework.context.ApplicationContextInitializer=\org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer# Application Listenersorg.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)了解下加载机制
