Springboot源码解析:SpringApplication的实例化
打个广告
个人想写《springboot源码解析》这一系列很久了,但是一直角儿心底的知识积累不足,所以一直没有动笔。
所以想找一些小伙伴一起写这一系列,互相纠错交流学习。
如果有小伙伴有兴趣一起把这一系列的讲解写完的话,加下我微信:13670426148,我们一起完成,当交流学习。
后期还想写一系列介绍rpc框架的,不过要再过一阵子了,先把springboot的写完
前言
这系列的教程从 Springboot项目的入口开始,即 SpringApplication.run(Application.class, args) 开始进行讲解。
启动入口
先贴一下入口类的代码:
@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()方法的时候会进行处理,可以先记住他们。
现在可以记住 @EnableAutoConfiguration
和 @EnableScheduling
和 @ComponentScan
等注解,记住这些注解,后面将介绍其运行过程。
SpringApplication 实例化过程
Application 这个类没有继承所有任何类,他真的就是一个 启动类,就相当与写算法题时候的那个main函数,而你的计算流程就写在其他类或者方法里面。
SpringApplication用于从java main方法引导和启动Spring应用程序,默认情况下,将执行以下步骤来引导我们的应用程序:
- 创建一个恰当的ApplicationContext实例(取决于类路径)
- 注册CommandLinePropertySource,将命令行参数公开为Spring属性
- 刷新应用程序上下文,加载所有单例bean
- 触发全部CommandLineRunner bean
大多数情况下,像SpringApplication.run(ShiroApplication.class, args);这样启动我们的应用,也可以在运行之前创建和自定义SpringApplication实例,具体可以参考注释中示例。
SpringApplication可以从各种不同的源读取bean。 通常建议使用单个@Configuration类来引导,但是我们也可以通过以下方式来设置资源:
- 通过AnnotatedBeanDefinitionReader加载完全限定类名
- 通过XmlBeanDefinitionReader加载XML资源位置,或者是通过GroovyBeanDefinitionReader加载groovy脚本位置
- 通过ClassPathBeanDefinitionScanner扫描包名称
- 也就是说SpringApplication还是做了不少事的,具体实现后续会慢慢讲来,今天的主角只是SpringApplication构造方法。
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项目,其中有一个不能被加载到,说明不是。
获取所有的实例化器Initializer.class
//看完这个方法真觉得很棒,获取工厂实例
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;
}
获取所有的监听器Initializer.class
类似的上面的步骤,监听器的获得结果如下: 图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() 的 “ 广播-事件-监听器” 的执行过程, 争取先吃透再解析。