spring boot 源码解析2-SpringApplication初始化

本文涉及的产品
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
简介: 使用过spring boot,spring cloud 的人都会在application.properties中配置如spring.datasource.url 的配置,但是其是如何生效的,很多人就不知道了

前⾔
我们⽣成⼀个spring boot 项⽬时,会⾃带⼀个启动类. 代码如下:

@SpringBootApplication
public class SpringBootAnalysisApplication {
 public static void main(String[] args) {
 SpringApplication.run(SpringBootAnalysisApplication.class, args);
 }
}

就是这么简单的代码,构成了spring boot的世界. 那么代码中只有⼀个@SpringBootApplication 注解 和 调⽤了SpringApplication#run
⽅法.那么我们先来解析SpringApplication的run⽅法.

解析
⾸先调⽤了org.springframework.boot.SpringApplication#run(Object, String...) ⽅法.代码如下:

public static ConfigurableApplicationContext run(Object source, String... args) {
 return run(new Object[] { source }, args);
}

接着调⽤如下代码:

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
 return new SpringApplication(sources).run(args);
}

可以发现 ⾸先初始化了SpringApplication,然后调⽤其实例⽅法:run.

  1. 在 SpringApplication 的构造器中,调⽤了 initialize ⽅法.
public SpringApplication(Object... sources) {
 initialize(sources);
}
  1. SpringApplication#initialize⽅法代码如下:
private void initialize(Object[] sources) {
 if (sources != null && sources.length > 0) {
 this.sources.addAll(Arrays.asList(sources));
 }
 this.webEnvironment = deduceWebEnvironment();
 setInitializers((Collection) getSpringFactoriesInstances(
 ApplicationContextInitializer.class));
 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
 this.mainApplicationClass = deduceMainApplicationClass();
}

可以看到做了如下5件事:

  1. 如果sources⻓度⼤于0的话,加⼊到SpringApplication的sources中,该sources是⼀个LinkedHashSet.
  2. 调⽤deduceWebEnvironment⽅法判断是否是web环境
  3. 设置initializers.
  4. 设置Listeners.
  5. 设置mainApplicationClass.
  6. deduceWebEnvironment代码如下:
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;
}

可以发现会调⽤ClassUtils类的isPresent⽅法,检查classpath中是否存在javax.servlet.Servlet类和
org.springframework.web.context.ConfigurableWebApplicationContext类,如果存在的话,返回true.否则返回false.

  1. 在设置Initializers时⾸先调⽤getSpringFactoriesInstances⽅法加载ApplicationContextInitializer.然后直接赋值给initializers.代码如下:
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保存names来避免重复元素
 Set<String> names = new LinkedHashSet<String>(
 SpringFactoriesLoader.loadFactoryNames(type, classLoader));
 // 根据names来进⾏实例化
 List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
 classLoader, args, names);
 // 对实例进⾏排序
 AnnotationAwareOrderComparator.sort(instances);
 return instances;
}

该⽅法逻辑如下:

  1. ⾸先获得ClassLoader.
  2. 调⽤SpringFactoriesLoader#loadFactoryNames进⾏加载,然后放⼊到LinkedHashSet进⾏去重.
  3. 调⽤createSpringFactoriesInstances进⾏初始化
  4. 排序
    其中SpringFactoriesLoader#loadFactoryNames代码如下:
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
 String factoryClassName = factoryClass.getName();
 try {
 Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURC
E_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(factoryClassN
ames)));
 }
 return result;
 }
 catch (IOException ex) {
 throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
 "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
 }
}

逻辑如下:

  1. 获得factoryClassName,对于当前来说factoryClassName =org.springframework.context.ApplicationContextInitializer.
  2. 通过传⼊的classLoader加载META-INF/spring.factories⽂件.
  3. 通过调⽤PropertiesLoaderUtils#loadProperties将其转为Properties.
  4. 获得factoryClassName对应的值进⾏返回.
    对于当前来说,由于我们只加⼊了spring-boot-starter-web的依赖,因此会加载如下的配置:
  5. 在spring-boot/META-INF/spring.factories中.org.springframework.context.ApplicationContextInitializer值如下:
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.context.embedded.ServerPortInfoApplicationContextInitializer
  1. 在spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories
    中.org.springframework.context.ApplicationContextInitializer值如下:
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer

因此会加载6个.
SpringApplication#createSpringFactoriesInstances⽅法如下:

private <T> List<T> createSpringFactoriesInstances(Class<T> type,
 Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
 Set<String> names) {
 List<T> instances = new ArrayList<T>(names.size());
 for (String name : names) {
 try {
 Class<?> instanceClass = ClassUtils.forName(name, classLoader);
 Assert.isAssignable(type, instanceClass);
 Constructor<?> constructor = instanceClass
 .getDeclaredConstructor(parameterTypes);
 T instance = (T) BeanUtils.instantiateClass(constructor, args);
 instances.add(instance);
 }
 catch (Throwable ex) {
 throw new IllegalArgumentException(
 "Cannot instantiate " + type + " : " + name, ex);
 }
 }
 return instances;
}

逻辑如下:遍历传⼊的names,也就是之前通过SpringFactoriesLoader加载的类名.通过遍历,依次调⽤其构造器进⾏初始化.加⼊到
instances.然后进⾏返回.
对于当前场景来说:
ConfigurationWarningsApplicationContextInitializer,DelegatingApplicationContextInitializer,ServerPortInfoApplicationContextInitializer
初始化没有做任何事.
ContextIdApplicationContextInitializer在初始化时.会获得spring boot的应⽤名.搜索路径如下:

  1. spring.application.name
  2. vcap.application.name
  3. spring.config.name
  4. 如果都没有配置的话,返回application.
    代码如下:
private static final String NAME_PATTERN = "${spring.application.name:${vcap.application.name:${s
pring.config.name:application}}}";
public ContextIdApplicationContextInitializer() {
 this(NAME_PATTERN);
}
public ContextIdApplicationContextInitializer(String name) {
 this.name = name;
}
  1. 设置SpringApplication#setListeners时,还是同样的套路.调⽤getSpringFactoriesInstances加载META-INF/spring.factories中配置
    的org.springframework.context.ApplicationListener. 对于当前来说.加载的类如下:
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener,\
org.springframework.boot.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.logging.LoggingApplicationListener

这些类在构造器中都没有做任何事.

  1. 调⽤SpringApplication#deduceMainApplicationClass⽅法.获得应⽤的启动类.该⽅法通过获取当前⽅法调⽤栈,找到main函数的
    类.代码如下:
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;
}

流程图如下:
QQ_20180515120216

参考内容:Spring Boot源码解析

相关文章
|
25天前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
25天前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
25天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
25天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
1天前
|
自然语言处理 数据处理 索引
mindspeed-llm源码解析(一)preprocess_data
mindspeed-llm是昇腾模型套件代码仓,原来叫"modelLink"。这篇文章带大家阅读一下数据处理脚本preprocess_data.py(基于1.0.0分支),数据处理是模型训练的第一步,经常会用到。
7 0
|
26天前
|
安全 搜索推荐 数据挖掘
陪玩系统源码开发流程解析,成品陪玩系统源码的优点
我们自主开发的多客陪玩系统源码,整合了市面上主流陪玩APP功能,支持二次开发。该系统适用于线上游戏陪玩、语音视频聊天、心理咨询等场景,提供用户注册管理、陪玩者资料库、预约匹配、实时通讯、支付结算、安全隐私保护、客户服务及数据分析等功能,打造综合性社交平台。随着互联网技术发展,陪玩系统正成为游戏爱好者的新宠,改变游戏体验并带来新的商业模式。
|
3月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,包括版本兼容性、安全性、性能调优等方面。
215 1
|
2月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。首先,创建并配置 Spring Boot 项目,实现后端 API;然后,使用 Ant Design Pro Vue 创建前端项目,配置动态路由和菜单。通过具体案例,展示了如何快速搭建高效、易维护的项目框架。
145 62
|
1月前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
126 13
|
1月前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。

热门文章

最新文章

推荐镜像

更多