Spring boot 源码分析(一)SpringApplication.run(上)
SpringApplication.run(Main.class, args);
从这个方法开始讲吧:
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
ConfigurableApplicationContext 这个接口,熟悉spring源码的童鞋们肯定一眼就会有亲切感
至于不熟悉的童鞋们嘛...未来我可能会再开一个spring的坑
这里不详细介绍了
回到这个方法本身:
方法内容很简单,指向了另一个run方法:
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
先创建了一个SpringApplication的对象,
然后再次指向了一个新的run方法。
不过在我们看下一个run方法之前,按照顺序,
我们先来看一下SpringApplication的这个构造方法:
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
//将resourceLoader属性设置为同名参数
this.resourceLoader = resourceLoader;
//判定primarySources是否为空,若为空则 throw new IllegalArgumentException("PrimarySources must not be null");
Assert.notNull(primarySources, "PrimarySources must not be null");
//确定primarySources不为空的前提下,将primarySource注入到属性primarySources中
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//设置属性webApplicationType
//关于deduceWebApplicationType()这个方法,暂且按下不表,后面会讲到
this.webApplicationType = deduceWebApplicationType();
//通过setter方法设置了initializers和listeners两个属性
//至于他到底设置了个什么东西嘛,后面会讲到
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//最后,设置属性mainApplicationClass
//deduceMainApplicationClass()这个方法我们后面来讲
this.mainApplicationClass = deduceMainApplicationClass();
}
简单总结一下就是:
进行了一轮初始化;
设置了一大堆不知道什么鬼用的参数
对象生成了以后,我们来看看这个run方法...
等等
本着刨根问底的精神,我们先解决前面悬而未决的问题:
关于deduceWebApplicationType();这个方法:
private WebApplicationType deduceWebApplicationType() {
//这里有三个参数
//REACTIVE_WEB_ENVIRONMENT_CLASS : dispatcherHandler的全类名
//MVC_WEB_ENVIRONMENT_CLASS : dispatcherServlet的全类名
//WEB_ENVIRONMENT_CLASSES : 是一个String[],里面有两项:
//一个是Servlet的全类名,一个是ConfigurableWebApplicationContext的全类名
//而isPresent方法,简单讲就是判断这个类是否存在,存在则返回true,反之返回false
//所以说spring boot项目中maven的dependency不能随便搞,不能多加也不能少加。
//spring boot会根据你的依赖来判断你这个项目的性质
//后面还会有很多这种根据依赖的不同来判定的地方
//然后说一下这个WebApplicationType:
//是一个枚举类,里面有三项:
//SERVLET,REACTIVE,NONE
//只起一个标识作用,标明你这个项目到底是一个servlet项目,还是reactive项目,还是不联网(none)的项目
if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
所以,前面构造器中:
this.webApplicationType = deduceWebApplicationType();
就是根据你的jar包依赖,设定了这个项目的类型
然后是我们的两个setter方法:
setInitializers 和 setListeners
你可能会很奇怪,为什么都有构造器了,还要用setter方法。
答案很简单啦,因为这个setter方法显然不会是那种简单的:
this.xx = xx;
这点东西而已啦。
虽然,倒也没复杂到哪去:
public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
this.initializers = new ArrayList<>();
this.initializers.addAll(initializers);
}
由以上代码可知,这个initializers就是一个ArrayList,把你参数中的itializers里面的所有项都放了进去。就酱;
public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
this.listeners = new ArrayList<>();
this.listeners.addAll(listeners);
}
除了变量名和参数泛型之外,好像就跟上面那个一模一样= =
两个setter方法讲完了,不过构造器中这两个setter方法的参数,是由一个叫getSpringFactoriesInstances这个方法
下一步我们来看这个方法:
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(这个是官方注释,意思是说用互不相同的名字来确保不发生重复)
//至于这个loadFactoryNames这个方法,简单讲就是根据接口或者抽象类的class对象和类加载器,查找所有对应的工厂类的全类名
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//顾名思义,createSpringFactoriesInstances就是创建Spring工厂的实例
//这个方法的具体代码我们等下来看
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
//简单讲,这就是排了个序。
//稍微详细点讲,根据对象的order value进行排序
//更详细的讲,等我开spring坑再说
//事先声明,这坑我不一定会开= =
//这坑太大了
//写spring是春天,讲spring是冬天
AnnotationAwareOrderComparator.sort(instances);
//排完序了,返回回去。
return instances;
}
所以上面的方法,实际重点就是那个createSpringFactoriesInstances。
再次说明一下上面那个方法中,我们调用createSpringFactoriesInstances方法时的五个参数:
type:一个Class对象。在前面的构造器中:
setInitializers方法里注入的是ApplicationContextInitializer.class这个Class对象,
setListeners方法里注入的是ApplicationListener.class这个Class对象。
paramerterTypes:一个Class数组。
在getSpringFactoriesInstances(Class<T> type)方法中,他注入的是一个空数组。
classLoader:类加载器。方法中用的是线程的类加载器。
args:一堆参数。
在getSpringFactoriesInstances(Class<T> type)方法中,他注入的是null。
names:一个LinkedHashSet<String>
由上面的type和classloader找到这个type所有对应的工厂类的全类名
好的请记住这些,来看下面的方法:
private <T> List<T> createSpringFactoriesInstances(
Class<T> type,
Class<?>[] parameterTypes,
ClassLoader classLoader,
Object[] args,
Set<String> names) {
//很显然,instances就是我们最终要返回的list
//从这个arraylist的范围来看,很显然这个list里的每一项都跟names有关
List<T> instances = new ArrayList<>(names.size());
for (String name : names) {
try {
//根据name和classloader来找类对象
//所以前面一定要保证这里的Names不一样不然list里面就会有重复的了。
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
//简单讲,看看type是不是instanceClass的超类或者接口。
//如果是,往下走;如果不是,报错。
Assert.isAssignable(type, instanceClass);
//如果代码执行到此,说明type是instanceClass的超类。
//parameterTypes由此可见是instanceClass的构造器的参数类型表
Constructor<?> constructor = instanceClass
.getDeclaredConstructor(parameterTypes);
//简单讲,就是用刚刚得到的构造器,和args来构造一个T的实例
//因此args和parameterTypes是一一对应的。
//前面已经判定过,type是instanceClass的超类或者接口。
//因此这个强制类型转换是成立的。
T instance = (T) BeanUtils.instantiateClass(constructor, args);
//把这个实例加到最开始声明的list里面
instances.add(instance);
} catch (Throwable ex) {
//前面如果报错了,那就再报个错
//spring向来如此,报错一个接着一个
//最终的效果就是,不管出了什么p大点儿错,都要先给你报满满一大长串的错
//然后每次看console...哎,头大
throw new IllegalArgumentException(
"Cannot instantiate " + type + " : " + name, ex);
}
}
//最终把最上面那个list返回回去
return instances;
}
综上所述,上面的方法就是创建了所有type所对应的工厂类的类对象。
此之谓createSpringFactoriesInstances
回到前面的构造方法:
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
就是将initializers初始化为ApplicationContextInitializer.class 所对应的所有工厂类的对象组成的集合
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
就是将listeners初始化为ApplicationListener.class所对应的所有工厂类的对象组成的集合
至此,构造方法完毕。
然后我们看run方法。。。
看个鬼啊,写这么多,你们看着累不?
我反正累了。
剩下的下次再说。
拜拜~~~~~~~~~
Spring boot 源码分析(一)SpringApplication.run(上)的更多相关文章
- spring boot源码分析之SpringApplication
spring boot提供了sample程序,学习spring boot之前先跑一个最简单的示例: /* * Copyright 2012-2016 the original author or au ...
- 精尽Spring Boot源码分析 - SpringApplication 启动类的启动过程
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- Spring Boot源码分析-启动过程
Spring Boot作为目前最流行的Java开发框架,秉承"约定优于配置"原则,大大简化了Spring MVC繁琐的XML文件配置,基本实现零配置启动项目. 本文基于Spring ...
- 精尽Spring Boot源码分析 - 内嵌Tomcat容器的实现
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - 支持外部 Tomcat 容器的实现
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - 配置加载
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - 日志系统
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- Spring Boot源码分析-配置文件加载原理
在Spring Boot源码分析-启动过程中我们进行了启动源码的分析,大致了解了整个Spring Boot的启动过程,具体细节这里不再赘述,感兴趣的同学可以自行阅读.今天让我们继续阅读源码,了解配置文 ...
- 精尽Spring Boot源码分析 - 序言
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - Jar 包的启动实现
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
随机推荐
- NOIP2009 t3 最优贸易
题目传送门:洛谷P1073 dalao们都用的tarjan啊拓扑排序啊之类的玩意儿,我这个蒟蒻不会,只想到了极其暴力的分层图最短路 设三个状态 0表示没有发生任何买卖的情况 1表示买了没有卖的情况 2 ...
- [BOI2007]Mokia 摩基亚
Description: 摩尔瓦多的移动电话公司摩基亚(Mokia)设计出了一种新的用户定位系统.和其他的定位系统一样,它能够迅速回答任何形如"用户C的位置在哪?"的问题,精确到毫 ...
- CSS(四)
css元素溢出 当子元素的尺寸超过父元素的尺寸时,需要设置父元素显示溢出的子元素的方式,设置的方法是通过overflow属性来设置. overflow的设置项: 1.visible 默认值.内容不会被 ...
- Module(CP343-1)Advanced system error SDB generation Error in rule file or rule file not found
报这个故障的原因是因为安装目录下缺少三个 err文件.只要从好的目录下拷贝过来即可. C:\Program Files (x86)\SIEMENS\Step7\s7wbx\rul
- (62)Wangdao.com第十天_JavaScript 变量的作用域
在 js 中有两种作用域:全局作用域,局部作用域. 全局作用域 直接写在 <script> 标签中的变量和方法. 在网页打开时创建,在网页关闭时销毁. 全局作用域有一个全局对象 windo ...
- [LeetCode] Rabbits in Forest 森林里的兔子
In a forest, each rabbit has some color. Some subset of rabbits (possibly all of them) tell you how ...
- uri&url
统一资源标志符URI就是在某一规则下能把一个资源独一无二地标识出来. 拿人做例子,假设这个世界上所有人的名字都不能重复,那么名字就是URI的一个实例,通过名字这个字符串就可以标识出唯一的一个人.现实当 ...
- 黑盒测试实践——day04
一.任务进展情况 通过昨天的选择和搜集资料,目前已成功安装好了testWriter,目前正在选择合适的web系统,进行测试. 二.存在的问题 安装TestWriter之前,需要安装SQLServre2 ...
- Python练手例子(6)
31.请输入星期几的第一个字母来判断一下是星期几,如果第一个字母一样,则继续判断第二个字母. 程序分析:用情况语句比较好,如果第一个字母一样,则判断用情况语句或if语句判断第二个字母. letter ...
- 已知一个正整数m,编写一个程序求m的反序数(待消化)
import java.util.Scanner; /** * @author:(LiberHome) * @date:Created in 2019/3/5 21:08 * @description ...