Springboot源码解析:SpringApplication的实例化java
我的想写《springboot源码解析》这一系列好久了,可是一直角儿心底的知识积累不足,因此一直没有动笔。 因此想找一些小伙伴一块儿写这一系列,互相纠错交流学习。web
若是有小伙伴有兴趣一块儿把这一系列的讲解写完的话,加下我微信:13670426148,咱们一块儿完成,当交流学习。算法
后期还想写一系列介绍rpc框架的,不过要再过一阵子了,先把springboot的写完spring
这系列的教程从 Springboot项目的入口开始,即 SpringApplication.run(Application.class, args) 开始进行讲解。springboot
先贴一下入口类的代码:微信
@SpringBootApplication
//@EnableTransactionManagement
@EnableAsync
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
@EnableScheduling
@EnableRetry
@ComponentScan(basePackages = {"*** ", "***"})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
复制代码
其中,入口类的类名是 Application, 这个类的类型将做为参数,传递给 SpringApplication的 run() 方法,还有一些初始化参数,这些都在run()方法的时候会进行处理,能够先记住他们。mybatis
如今能够记住 @EnableAutoConfiguration
和 @EnableScheduling
和 @ComponentScan
等注解,记住这些注解,后面将介绍其运行过程。框架
Application 这个类没有继承全部任何类,他真的就是一个 启动类,就至关与写算法题时候的那个main函数,而你的计算流程就写在其余类或者方法里面。less
SpringApplication用于从java main方法引导和启动Spring应用程序,默认状况下,将执行如下步骤来引导咱们的应用程序:函数
大多数状况下,像SpringApplication.run(ShiroApplication.class, args);这样启动咱们的应用,也能够在运行以前建立和自定义SpringApplication实例,具体能够参考注释中示例。
SpringApplication能够从各类不一样的源读取bean。 一般建议使用单个@Configuration类来引导,可是咱们也能够经过如下方式来设置资源:
public class SpringApplication{
public SpringApplication(ResourceLoader resourceLoader, Object... sources) {
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.headless = true;
this.registerShutdownHook = true;
//todo -------------------------------------------------------
this.additionalProfiles = new HashSet();
//上面的信息都不是主要的,主要的信息在这里,在这里进行
//(1)运行环境 (2) 实例化器 (3)监听器等的初始化过程,下面将详细解析
this.initialize(sources);
}
public ConfigurableApplicationContext run(String... args) {
*******
}
}
复制代码
这个 this.initialize(sources) 方法仍是在 SpringApplication里面的,因此这个SpringApplication真的是贯穿springboot整个启动过程的一个类,后面还有一个run() 方法。
咱们来看 initialize(Object[] sources) 方法的内容
private void initialize(Object[] sources) {
//sources里面就是咱们的入口类: Application.class
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
//这行代码设置SpringApplication的属性webEnvironment,deduceWebEnvironment方法是推断是不是web应用的核心方法
this.webEnvironment = this.deduceWebEnvironment();
//获取全部的实例化器Initializer.class this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
//获取全部的监听器
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
//这个不解释了,就是咱们的Application.class ,咱们写的入口类,过程就是从当前的堆栈中找到咱们写main方法额类,就是获取咱们的入口类了
this.mainApplicationClass = this.deduceMainApplicationClass();
}
复制代码
下面就解释3个部分的具体实现:
(1) 推测运行环境
(2)获取全部的实例化器Initializer.class
又展现了其获取过程
(3)获取全部的监听器Initializer.class
推测运行环境,并赋予个 this.webEnvironment 这个属性, deduceWebEnvironment方法是推断是不是web应用的核心方法。 在后面SpringApplication 的run()方法中建立 ApplicationContext 的时候就是根据webEnvironment这个属性来判断是 AnnotationConfigEmbeddedWebApplicationContext
仍是 AnnotationConfigApplicationContext
代码以下:
private boolean deduceWebEnvironment() {
String[] var1 = WEB_ENVIRONMENT_CLASSES;
int var2 = var1.length;
for(int var3 = 0; var3 < var2; ++var3) {
String className = var1[var3];
if (!ClassUtils.isPresent(className, (ClassLoader)null)) {
return false;
}
}
return true;
}
WEB_ENVIRONMENT_CLASSES = new String[]{
"javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext"
};
复制代码
推断过程很简单,不过我不理解为何这么写,由于我这个是web项目,因此 this.webEnvironment
的值为true
ClassUtils.isPresent()的过程其实很简单,就是判断 WEB_ENVIRONMENT_CLASSES
里面的两个类能不能被加载,若是能被加载到,则说明是web项目,其中有一个不能被加载到,说明不是。
//看完这个方法真以为很棒,获取工厂实例
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) {
return this.getSpringFactoriesInstances(type, new Class[0]);
}
//记住 ty
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
//这个是获取类加载器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
//type是ApplicationContextInitializer.class,获取类型工厂的名字
Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//获取工厂实例
List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
//排序,按照@Order的顺序进行排序,没有@Order的话,就按照本来的顺序进行排序,无论他问题不大
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
复制代码
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
try {
//获取全部 jar包下面的 META-INF/spring.factories 的urls
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
ArrayList result = new ArrayList();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
//每一个spring.factories里的下的内容装载成Properties信息
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
//下面内容会继续解析
String factoryClassNames = properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
} catch (IOException var8) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);
}
}
复制代码
图1.1以下:
还有不少,就不一一列举出来了
(1)就是找到全部的 /MEIT-INF下面的spring.factory
(2)转换成 properties,
(3)properties.getProperty(factoryClassName)
关于 /MEIT-INF/spring.factory,不知道你们有没有写过 starter,若是不知道是什么,不少依赖好比mybatis-plus 、springboot的包里面都有不少依赖,打成starter的形式,被咱们springboot项目依赖。
能够查一查springboot自定义starter,看一下,大概就知道一个spring.factory的做用了。。
此时 factoryClassName 至关因而一个key获取对应的properties里面是否有 "org.springframework.context.ApplicationContextInitializer"对应的值,有的话,添加到result中
到最后能够获得的有,即图1.2
再进行 获取工厂实例 操做,步骤很简单,就是用构造器和类名生成指定的 Inializer, 到如今的过程都很简单。
贴个代码,不进行解释了
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) {
List<T> instances = new ArrayList(names.size());
Iterator var7 = names.iterator();
while(var7.hasNext()) {
String name = (String)var7.next();
try {
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
T instance = BeanUtils.instantiateClass(constructor, args);
instances.add(instance);
} catch (Throwable var12) {
throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, var12);
}
}
return instances;
}
复制代码
相似的上面的步骤,监听器的得到结果以下: 图1.3
(1)还记得SpringApplication.class有那些属性吗
public class SpringApplication{
private List<ApplicationContextInitializer<?>> initializers; //如图1.2这个是拿6个Initializer
private WebApplicationType webApplicationType; //这个是true
private List<ApplicationListener<?>> listeners; //这和是图1.3的10个Listener
private Class<?> mainApplicationClass; //结果就是DemoApplication
//另外还有构造方法设置的值
public SpringApplication(ResourceLoader resourceLoader, Object... sources) {
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.headless = true;
this.registerShutdownHook = true;
//todo -------------------------------------------------------
this.additionalProfiles = new HashSet();
//上面的信息都不是主要的,主要的信息在这里,在这里进行
//(1)运行环境 (2) 实例化器 (3)监听器等的初始化过程,下面将详细解析
this.initialize(sources);
}
}
复制代码
(2) SpringApplication.class 就是一个操做启动过程的类
实例化过程就是加载一些最初始的参数和信息,好比监听器,实例化器,bannerMode,additionalProfiles等信息。其中最主要的仍是监听器和实例化器, 关于监听器,是springboot启动过程最重要的一部分,其启动过程的机制大概就是, 用一个广播,他广播一些event事件,而后这些监听器(10个),就会根据这些事件,作不一样的反应。 监听器模式你们能够先了解一下。
下面会讲 SpringApplication.run() 里面的内容了,主要run() 的 “ 广播-事件-监听器” 的执行过程, 争取先吃透再解析。