springboot启动流程
一、简述
Spring Boot启动流程分析使用版本:
- SpringBoot VERSION:版本 2.7.9;
- JDK1.8
Spring Boot项目的Application启动类:
可以看出Application启动类中,包含了@SpringBootApplication 注解和 SpringApplication.run 启动方法,所以SpringBoot的启动可以分解为“注解”和 “启动方法”两大过程,其中启动方法中又可以分为两个阶段即创建SpringApplication实例和执行run方法。
二、注解
@SpringBootApplication注解内部可以发现,它虽然定义使用了多个Annotation,但实际上重要的只有三个Annotation:
- @SpringBootConfiguration(@SpringBootConfiguration注解点开查看发现里面还是应用了@Configuration)->Spring IOC容器配置类;
- @EnableAutoConfiguration ->使用@Import将所有符合自动配置条件的bean定义加载到IOC容器;
- @ComponentScan ->自动扫描并加载符合条件的组件或者bean定义,默认扫描SpringApplication的run方法里的class所在的包路径下文件,所以通常将该启动类放到根包路径下。
三、启动
1. 运行 SpringApplication.run() 方法
一般来说springboot的应用程序都是从run方法开始的
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootDemo279Application {
public static void main(String[] args) {
//启动spring boot
SpringApplication.run(SpringBootDemo279Application.class, args);
}
}
进入run方法后,会 new 一个SpringApplication 对象,创建这个对象的构造函数做了一些准备工作,编号第2~5步就是构造函数里面所做的事情
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
/**
* Create a new {@link SpringApplication} instance. The application context will load
* beans from the specified primary sources (see {@link SpringApplication class-level}
* documentation for details). The instance can be customized before calling
* {@link #run(String...)}.
* @param resourceLoader the resource loader to use
* @param primarySources the primary bean sources
* @see #run(Class, String[])
* @see #setSources(Set)
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 初始化类加载器
this.resourceLoader = resourceLoader;
// Assert 断言非空,若传入的class参数为null则打印异常并退出初始化
Assert.notNull(primarySources, "PrimarySources must not be null");
// 获取main方法中的args,初始化启动时配置的额外参数集合
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 2.判断项目启动类型:NONE/SERVLET/REACTIVE
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 3.加载初始化器
this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//4.加载监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//5.推导出主应用程序类,即从当前的栈信息中寻找main所在主类
this.mainApplicationClass = deduceMainApplicationClass();
}
2. 确定应用程序启动类型
在SpringApplication的构造方法内,首先会通过 WebApplicationType.deduceFromClasspath(); 方法判断当前应用程序的容器,默认使用的是Servlet 容器,除了servlet之外,还有NONE 和 REACTIVE (响应式项目);
...
// 2.判断项目启动类型:NONE/SERVLET/REACTIVE
this.webApplicationType = WebApplicationType.deduceFromClasspath();
....
public enum WebApplicationType {
/**
* The application should not run as a web application and should not start an
* embedded web server.
*/
NONE,
/**
* The application should run as a servlet-based web application and should start an
* embedded servlet web server.
*/
SERVLET,
/**
* The application should run as a reactive web application and should start an
* embedded reactive web server.
*/
REACTIVE;
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
/**
* deduceFromClasspath
* 依次循环遍历当前应用中是否存在相关的类来判断最终应用的启动类型
*
* @return
*/
static WebApplicationType deduceFromClasspath() {
/**
* REACTIVE:响应式WEB项目
* 若启动类型为REACTIVE,
* 则类路径下存在 org.springframework.web.reactive.DispatcherHandler 类
* 并且不存在 org.springframework.web.servlet.DispatcherServlet 和 org.glassfish.jersey.servlet.ServletContainer
* 两者指的是SpringMVC/Tomcat和jersey容器
*/
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
/**
* NONE:非WEB项目,就是一个最简单的Springboot应用
* 若启动类型为NONE
* 则类路径下 javax.servlet.Servlet 和org.springframework.web.context.ConfigurableWebApplicationContext都不存在
*/
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
/**
* SERVLET:SERVLET WEB 项目
* 若启动类型为Servlet,则必须有SERVLET_INDICATOR_CLASSES中的javax.servlet.Servlet
* 和org.springframework.web.context.ConfigurableWebApplicationContext
*/
return WebApplicationType.SERVLET;
}
}
3. 加载所有的初始化器
从 META-INF/spring.factories 配置文件加载springboot初始化器
...
// 3.加载初始化器
this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
...
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
/**
* 加载各jar包中的"META-INF/spring.factories"配置
* 其中SpringFactoriesLoader.loadFactoryNames(type, classLoader) 方法
* 是获取spring.factories配置文件中已经配置的指定类型的的实现类集合
* 其中FACTORIES_RESOURCE_LOCATION的值:META-INF/spring.factories
*/
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 通过反射创建这些类
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
// 排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
...
/**
* Load the fully qualified class names of factory implementations of the
* given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
* class loader.
* <p>As of Spring Framework 5.3, if a particular implementation class name
* is discovered more than once for the given factory type, duplicates will
* be ignored.
* @param factoryType 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
* @throws IllegalArgumentException if an error occurs while loading factory names
* @see #loadFactories
*/
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
/**
* Springboot自动配置的秘密
* Springboot在启动时读取了所有starter jar包里的META-INF/spring.factories配置文件,实现了所谓的自动化配置
* 这里jar包里的都是默认配置,后续Springboot也会从xml、yaml文件中的用户配置去覆盖同名的配置。
* 另外,这里的缓存配置是保存在一个map类型的cache中,其中的key键对应上面提到的各种Type类型,value就是Type的各种初始jar包里的同类 型Java类。
*/
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
// 获取相应类加载器中内容
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}
result = new HashMap<>();
try {
/**
* 获取资源 -> META-INF/spring.factories 列表
* 其中FACTORIES_RESOURCE_LOCATION的值:META-INF/spring.factories
*/
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
// 可能存在多个META-INF/spring.factories 文件,循环加载
while (urls.hasMoreElements()) {
// 获取 META-INF/spring.factories 文件URL地址
URL url = urls.nextElement();
// 加载资源
UrlResource resource = new UrlResource(url);
// 加载资源配置
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
// key:value形式循环配置
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
// 逗号分隔列表到字符串数组
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
// 循环value中子项到列表中
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
// Replace all lists with unmodifiable lists containing unique elements
// 列表去重
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
// 列表保存
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;
}
4. 加载所有的监听器
加载监听器也是从 META-INF/spring.factories 配置文件中加载的,与初始化不同的是,监听器加载的是实现了 ApplicationListener 接口的类
...
//4.加载监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
...
getSpringFactoriesInstances()同上,参数不同
5. 获取程序运行的主类
deduceMainApplicationClass(); 这个方法仅仅是找到main方法所在的类,为后面的扫包作准备,deduce是推断的意思,所以准确地说,这个方法作用是推断出主方法所在的类;
...
//5.推导出主应用程序类,即从当前的栈信息中寻找main所在主类
this.mainApplicationClass = deduceMainApplicationClass();
...
/**
* 推导主应用程序类
* @return
*/
private Class<?> deduceMainApplicationClass() {
try {
// 获取当前的栈信息
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
// 获取main方法所在的类class
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
6. 开启计时
程序运行到这里,就已经进入了run方法的主体了,第一步调用的run方法是静态方法,那个时候还没实例化SpringApplication对象,现在调用的run方法是非静态的,是需要实例化后才可以调用的
/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
*/
public ConfigurableApplicationContext run(String... args) {
//6.开始计时
long startTime = System.nanoTime();
//创建启动上下文对象即spring根容器
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
// 定义可配置的应用程序上下文变量
ConfigurableApplicationContext context = null;
/**
* 7.设置jdk系统属性
* headless直译就是无头模式,
* headless模式的意思就是明确Springboot要在无鼠键支持的环境中运行,一般程序也都跑在Linux之类的服务器上,无鼠键支持,这里默认值是true;
*/
configureHeadlessProperty();
/**
* 8.获取运行监听器 getRunListeners, 其中也是调用了上面说到的getSpringFactoriesInstances 方法
* 从spring.factories中获取配置
*/
SpringApplicationRunListeners listeners = getRunListeners(args);
// 启动监听器
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
// 9.设置应用程序参数,也就是在命令行下启动应用带的参数,如--server.port=9000
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
/**
* 10.准备环境 prepareEnvironment,里面主要涉及到
* getOrCreateEnvironment、configureEnvironment、configurePropertySources、configureProfiles
* environmentPrepared、bindToSpringApplication、attach诸多方法可以在下面的例子中查看
*/
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
// 配置忽略的 bean
configureIgnoreBeanInfo(environment);
// 11.打印 SpringBoot 标志,即启动的时候在控制台的图案logo,可以在src/main/resources下放入名字是banner的自定义文件
Banner printedBanner = printBanner(environment);
// 12.创建应用上下文(IOC 容器)
context = createApplicationContext();
// 设置一个启动器,设置应用程序启动
context.setApplicationStartup(this.applicationStartup);
// 13.准备应用程序上下文(配置 IOC 容器的基本信息 (spring容器前置处理))
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
/**
* 14.刷新IOC容器
* 这里会涉及Spring容器启动、自动装配、创建 WebServer启动Web服务即SpringBoot启动内嵌的 Tomcat
*/
refreshContext(context);
/**
* 15.留给用户自定义容器刷新完成后的处理逻辑
* 刷新容器后的扩展接口(spring容器后置处理)
*/
afterRefresh(context, applicationArguments);
// 16.结束计时器并打印,这就是我们启动后console的显示的时间
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
// 打印启动完毕的那行日志
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
// 17.发布监听应用上下文启动完成(发出启动结束事件),所有的运行监听器调用 started() 方法
listeners.started(context, timeTakenToStartup);
// 18.执行runner,遍历所有的 runner,调用 run 方法
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
// 异常处理,如果run过程发生异常
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
// 返回最终构建的容器对象
return context;
}
7.设置java.awt.headless为true
这里将java.awt.headless设置为true,表示运行在服务器端,在没有显示器器和鼠标键盘的模式下照样可以工作,模拟输入输出设备功能
...
/**
* 7.设置jdk系统属性
* headless直译就是无头模式,
* headless模式的意思就是明确Springboot要在无鼠键支持的环境中运行,一般程序也都跑在Linux之类的服务器上,无鼠键支持,这里默认值是true;
*/
configureHeadlessProperty();
...
/**
* headless直译就是无头模式,
* headless模式的意思就是明确Springboot要在无鼠键支持的环境中运行,一般程序也都跑在Linux之类的服务器上,无鼠键支持,这里默认值是true;
*/
private void configureHeadlessProperty() {
System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}
8.获取并启用监听器
这一步 通过监听器来实现初始化的的基本操作,这一步做了2件事情
- 创建所有 Spring 运行监听器并发布应用启动事件
- 启用监听器
/**
* 8.获取运行监听器 getRunListeners, 其中也是调用了上面说到的getSpringFactoriesInstances 方法
* 从spring.factories中获取配置
*/
SpringApplicationRunListeners listeners = getRunListeners(args);
// 启动监听器
listeners.starting(bootstrapContext, this.mainApplicationClass);
9.设置应用程序参数
将执行run方法时传入的参数封装成一个对象
// 9.设置应用程序参数,也就是在命令行下启动应用带的参数,如--server.port=9000
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
10.准备环境变量
准备环境变量,包含系统属性和用户配置的属性等,里面主要涉及到getOrCreateEnvironment、configureEnvironment、configurePropertySources、configureProfilesenvironmentPrepared、bindToSpringApplication、attach诸多方法
...
/**
* 10.准备环境 prepareEnvironment,里面主要涉及到
* getOrCreateEnvironment、configureEnvironment、configurePropertySources、configureProfiles
* environmentPrepared、bindToSpringApplication、attach诸多方法可以在下面的例子中查看
*/
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
...
/**
* 准备环境
*
* @param listeners
* @param bootstrapContext
* @param applicationArguments
* @return
*/
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// Create and configure the environment 创建和配置环境
// 根据项目类型建环境ConfigurableEnvironment
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 从环境中获取并设置 PropertySources 和 activeProfiles
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 把 PropertySources 设置在自己PropertySources的第一个位置
ConfigurationPropertySources.attach(environment);
/**
* 运行监听器调用
* 广播事件,listeners环境准备(就是广播ApplicationEnvironmentPreparedEvent事件)
* 发布事件通知所有的监听器当前环境准备完成
*/
listeners.environmentPrepared(bootstrapContext, environment);
// 移动 defaultProperties 属性源到环境中的最后一个源
DefaultPropertiesPropertySource.moveToEnd(environment);
// 断言 抛异常
Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
"Environment prefix cannot be set via properties.");
// 与容器绑定当前环境
bindToSpringApplication(environment);
// 若非web环境,将环境转换成StandardEnvironment
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
// 配置PropertySources对它自己的递归依赖
ConfigurationPropertySources.attach(environment);
return environment;
}
/**
* 获取或创建环境Environment
*
* @return
*/
private ConfigurableEnvironment getOrCreateEnvironment() {
// 存在则直接返回
if (this.environment != null) {
return this.environment;
}
/**
* 根据webApplicationType创建对应的Environment
*/
switch (this.webApplicationType) {
// SERVLET WEB 项目
case SERVLET:
return new ApplicationServletEnvironment();
// REACTIVE:响应式WEB项目
case REACTIVE:
return new ApplicationReactiveWebEnvironment();
// 非WEB项目,就是一个最简单的Springboot应用
default:
return new ApplicationEnvironment();
}
}
/**
* 从环境中获取并设置 PropertySources 和 activeProfiles
* 将配置任务按顺序委托给configurePropertySources和configureProfiles
* Template method delegating to
* {@link #configurePropertySources(ConfigurableEnvironment, String[])} and
* {@link #configureProfiles(ConfigurableEnvironment, String[])} in that order.
* Override this method for complete control over Environment customization, or one of
* the above for fine-grained control over property sources or profiles, respectively.
*
* @param environment this application's environment
* @param args arguments passed to the {@code run} method
* @see #configureProfiles(ConfigurableEnvironment, String[])
* @see #configurePropertySources(ConfigurableEnvironment, String[])
*/
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
if (this.addConversionService) {
environment.setConversionService(new ApplicationConversionService());
}
// 配置PropertySources
configurePropertySources(environment, args);
// 配置Profiles
configureProfiles(environment, args);
}
/**
* 配置PropertySources
* Add, remove or re-order any {@link PropertySource}s in this application's
* environment.
*
* @param environment this application's environment
* @param args arguments passed to the {@code run} method
* @see #configureEnvironment(ConfigurableEnvironment, String[])
*/
protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
MutablePropertySources sources = environment.getPropertySources();
// 初始化 defaultProperties
if (!CollectionUtils.isEmpty(this.defaultProperties)) {
// 存在的话将其放到最后位置
DefaultPropertiesPropertySource.addOrMerge(this.defaultProperties, sources);
}
/**
* 存在命令行参数,则解析它并封装进SimpleCommandLinePropertySource对象
* 同时将此对象放到sources的第一位置(优先级最高)
*/
if (this.addCommandLineProperties && args.length > 0) {
String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
if (sources.contains(name)) {
PropertySource<?> source = sources.get(name);
CompositePropertySource composite = new CompositePropertySource(name);
composite.addPropertySource(
new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
composite.addPropertySource(source);
sources.replace(name, composite);
} else {
// 放到首位
sources.addFirst(new SimpleCommandLinePropertySource(args));
}
}
}
/**
* 配置Profiles
*
* @param environment
* @param args
*/
protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
/**
* 保证environment的activeProfiles属性被初始化了。从PropertySources中查找spring.profiles.active属性
* 存在则将其值添加activeProfiles集合中。
* 配置应用环境中的哪些配置文件处于激活状态(或默认激活)
* 可以通过spring.profiles.active属性在配置文件处理期间激活其他配置文件
* 就是我们项目中通常配置的dev、sit、prod等环境配置信息设置哪些Profiles是激活的。
*/
environment.getActiveProfiles(); // ensure they are initialized
// But these ones should go first (last wins in a property key clash)
// 如果存在其他的Profiles,则将这些Profiles放到第一的位置
Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
environment.setActiveProfiles(StringUtils.toStringArray(profiles));
}
/**
* 运行监听器调用
*
* @param bootstrapContext
* @param environment
*/
void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
doWithListeners("spring.boot.application.environment-prepared",
(listener) -> listener.environmentPrepared(bootstrapContext, environment));
}
/**
* 运行监听器调用
* Called once the environment has been prepared, but before the
* {@link ApplicationContext} has been created.
*
* @param environment the environment
* @deprecated since 2.4.0 for removal in 2.6.0 in favor of
* {@link #environmentPrepared(ConfigurableBootstrapContext, ConfigurableEnvironment)}
*/
@Deprecated
default void environmentPrepared(ConfigurableEnvironment environment) {
for (SpringApplicationRunListener listener : this.listeners) {
// 广播ApplicationEnvironmentPreparedEvent事件,后面再看
listener.environmentPrepared(environment);
}
}
/**
* 与容器绑定当前环境
* Bind the environment to the {@link SpringApplication}.
*
* @param environment the environment to bind
*/
protected void bindToSpringApplication(ConfigurableEnvironment environment) {
try {
// 将environment绑定到SpringApplication
Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
} catch (Exception ex) {
throw new IllegalStateException("Cannot bind to SpringApplication", ex);
}
}
/**
* 配置PropertySources对它自己的递归依赖
* Attach a {@link ConfigurationPropertySource} support to the specified
* {@link Environment}. Adapts each {@link PropertySource} managed by the environment
* to a {@link ConfigurationPropertySource} and allows classic
* {@link PropertySourcesPropertyResolver} calls to resolve using
* {@link ConfigurationPropertyName configuration property names}.
* <p>
* The attached resolver will dynamically track any additions or removals from the
* underlying {@link Environment} property sources.
*
* @param environment the source environment (must be an instance of
* {@link ConfigurableEnvironment})
* @see #get(Environment)
*/
public static void attach(Environment environment) {
// 判断environment是否是ConfigurableEnvironment的实例
Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
// 从environment获取PropertySources
MutablePropertySources sources = ((ConfigurableEnvironment) environment)
.getPropertySources();
PropertySource<?> attached = sources.get(ATTACHED_PROPERTY_SOURCE_NAME);
if (attached != null && attached.getSource() != sources) {
sources.remove(ATTACHED_PROPERTY_SOURCE_NAME);
attached = null;
}
if (attached == null) {
// 将sources封装成ConfigurationPropertySourcesPropertySource对象,并把这个对象放到sources的第一位置
sources.addFirst(new ConfigurationPropertySourcesPropertySource(
ATTACHED_PROPERTY_SOURCE_NAME,
new SpringConfigurationPropertySources(sources)));
}
}
11.打印banner信息
printBanner 打印SpringBoot标志。printBanner(environment)方法就是打印Banner,Banner就是项目启动时看到的那个logo。在工程项目src/main/resources路径下下放入名字是banner的文件,后缀后可以是SpringApplicationBannerPrinter.java类里的{ “gif”, “jpg”, “png” },或者是txt、图片也可以的,但是图片打印时会字符化,而不是打印图片本身。banner信息生成
...
// 11.打印 SpringBoot 标志,即启动的时候在控制台的图案logo,可以在src/main/resources下放入名字是banner的自定义文件
Banner printedBanner = printBanner(environment);
...
/**
* 打印SpringBoot标志
* banner的输出默认有三种种模式,LOG、CONSOLE、OFF。
* 1. LOG:将banner信息输出到日志文件。
* 2. CONSOLE:将banner信息输出到控制台。
* 3. OFF:禁用banner的信息输出。
*
* @param environment
* @return
*/
private Banner printBanner(ConfigurableEnvironment environment) {
// 判断Banner的模式是否关闭,如果关闭直接返回。
if (this.bannerMode == Banner.Mode.OFF) {
return null;
}
ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader
: new DefaultResourceLoader(null);
// 创建SpringApplicationBannerPrinter 打印类
SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);
// LOG:将banner信息输出到日志文件
if (this.bannerMode == Mode.LOG) {
return bannerPrinter.print(environment, this.mainApplicationClass, logger);
}
//banner没有关闭且没有指定是写到log文件中 将banner信息输出到控制台
return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}
/**
* 打印
*
* @param environment
* @param sourceClass
* @param logger
* @return
*/
Banner print(Environment environment, Class<?> sourceClass, Log logger) {
// 获取banner内容
Banner banner = getBanner(environment);
try {
logger.info(createStringFromBanner(banner, environment, sourceClass));
} catch (UnsupportedEncodingException ex) {
logger.warn("Failed to create String for banner", ex);
}
return new PrintedBanner(banner, sourceClass);
}
/**
* 获取banner内容
*
* @param environment
* @return
*/
private Banner getBanner(Environment environment) {
Banners banners = new Banners();
// 图片类型的banner内容
banners.addIfNotNull(getImageBanner(environment));
// 文本类型的banner内容
banners.addIfNotNull(getTextBanner(environment));
if (banners.hasAtLeastOneBanner()) {
return banners;
}
if (this.fallbackBanner != null) {
return this.fallbackBanner;
}
return DEFAULT_BANNER;
}
static final String BANNER_LOCATION_PROPERTY = "spring.banner.location";
static final String DEFAULT_BANNER_LOCATION = "banner.txt";
/**
* 文本类型的banner内容获取
*
* @param environment
* @return
*/
private Banner getTextBanner(Environment environment) {
/**
* 拿到自定义配置的banner文件地址
* BANNER_LOCATION_PROPERTY = "spring.banner.location"
* DEFAULT_BANNER_LOCATION = "banner.txt";
*/
String location = environment.getProperty(BANNER_LOCATION_PROPERTY, DEFAULT_BANNER_LOCATION);
Resource resource = this.resourceLoader.getResource(location);
try {
if (resource.exists() && !resource.getURL().toExternalForm().contains("liquibase-core")) {
return new ResourceBanner(resource);
}
} catch (IOException ex) {
// Ignore
}
return null;
}
12. 创建应用程序的上下文
实例化应用程序的上下文, 调用 createApplicationContext() 方法
// 12.创建应用上下文(IOC 容器)
context = createApplicationContext();
13.准备应用上下文
13.1. 实例化单例的beanName生成器
在 postProcessApplicationContext(context); 方法里面。使用单例模式创建 了BeanNameGenerator 对象,其实就是beanName生成器,用来生成bean对象的名称
13.2. 执行初始化方法
执行之前加载出来的所有初始化器,实现了ApplicationContextInitializer 接口的类
13.3. 将启动参数注册到容器中
这里将启动参数以单例的模式注册到容器中,是为了以后方便拿来使用,参数的beanName 为 :springApplicationArguments
...
// 13.准备应用程序上下文(配置 IOC 容器的基本信息 (spring容器前置处理))
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
...
/**
* 准备IOC容器基本信息
* @param bootstrapContext
* @param context
* @param environment
* @param listeners
* @param applicationArguments
* @param printedBanner
*/
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
// 设置容器环境,包括各种变量
context.setEnvironment(environment);
/**
* 后置处理流程
* 设置IOC容器的 bean 生成器和资源加载器
*/
postProcessApplicationContext(context);
/**
* 获取所有的初始化器调用 initialize() 方法进行初始化
* 执行容器中的ApplicationContextInitializer(包括从 spring.factories和自定义的实例)初始化
*/
applyInitializers(context);
/**
* 触发所有 SpringApplicationRunListener 监听器的 contextPrepared 事件方法
* 所有的运行监听器调用 environmentPrepared() 方法,EventPublishingRunListener 发布事件通知 IOC 容器准备完成
*/
listeners.contextPrepared(context);
bootstrapContext.close(context);
// 打印启动日志
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 注册添加特定的单例bean
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// Load the sources
// 加载所有资源
Set<Object> sources = getAllSources();
// 断言资源费控
Assert.notEmpty(sources, "Sources must not be empty");
// 创建BeanDefinitionLoader,加载启动类,将启动类注入容器
load(context, sources.toArray(new Object[0]));
// 触发所有 SpringApplicationRunListener 监听器的 contextLoaded 事件方法
listeners.contextLoaded(context);
}
14. 刷新应用上下文
refresh 刷新应用上下文,即刷新Spring上下文信息refreshContext。这里会涉及Spring容器启动、SpringBoot自动装配、创建 WebServer启动Web服务即SpringBoot启动内嵌的 Tomcat。
...
/**
* 14.刷新IOC容器
* 这里会涉及Spring容器启动、自动装配、创建 WebServer启动Web服务即SpringBoot启动内嵌的 Tomcat
*/
refreshContext(context);
...
/**
* 刷新应用上下文
*
* @param context
*/
private void refreshContext(ConfigurableApplicationContext context) {
if (this.registerShutdownHook) {
// 判断是否注册关闭的钩子,是则注册钩子
shutdownHook.registerApplicationContext(context);
}
refresh(context);
}
/**
* Refresh the underlying {@link ApplicationContext}.
*
* @param applicationContext the application context to refresh
*/
protected void refresh(ConfigurableApplicationContext applicationContext) {
applicationContext.refresh();
}
/**
* 刷新IOC容器
*
* @throws BeansException
* @throws IllegalStateException
*/
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
// Prepare this context for refreshing. 准备刷新上下文
prepareRefresh();
// Tell the subclass to refresh the internal bean factory. 通知子类刷新内部工厂
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context. 准备Bean工厂
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
// 允许在上下文子类中对bean工厂进行后处理,这部分涉及Web服务器的启动,如servlet
postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// Invoke factory processors registered as beans in the context.
// 调用在上下文中注册为 bean 的工厂处理器
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation. 注册拦截 bean 创建的 bean 处理器
registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
// Initialize message source for this context. 初始化此上下文的消息源
initMessageSource();
// Initialize event multicaster for this context. 为该上下文初始化事件多播器
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses. 初始化特定上下文子类中的其他特殊 bean
/**
* SpringBoot 一键启动web工程的关键方法
* 创建 WebServer启动Web服务
* SpringBoot启动内嵌的 Tomcat 首先要在pom文件配置内嵌容器为tomcat
* SpringBoot 嵌入式 Servlet 容器,默认支持的 webServe:Tomcat、Jetty、Undertow
* <exclusion>
* <groupId>org.springframework.boot</groupId>
* <artifactId>spring-boot-starter-tomcat</artifactId>
* </exclusion>
*/
onRefresh();
// Check for listener beans and register them. 检查侦听器 bean 并注册
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons. 实例化所有剩余的(非延迟初始化)单例
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event. 发布事件
finishRefresh();
} catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources. 销毁bean
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
} finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
contextRefresh.end();
}
}
}
15.刷新应用上下文后续处理(可扩展)
afterRefresh 方法是启动后的一些处理,留给用户扩展使用,目前这个方法里面是空的,
...
/**
* 15.留给用户自定义容器刷新完成后的处理逻辑
* 刷新容器后的扩展接口(spring容器后置处理)
*/
afterRefresh(context, applicationArguments);
...
/**
* Called after the context has been refreshed.
* @param context the application context
* @param args the application arguments
*/
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
}
16.结束计时
到这一步,springboot其实就已经完成了,打印启动springboot的时长
// 16.结束计时器并打印,这就是我们启动后console的显示的时间
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
// 打印启动完毕的那行日志
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
17.发布上下文准备就绪事件
started 发布监听应用启动事件。
...
// 17.发布监听应用上下文启动完成(发出启动结束事件),所有的运行监听器调用 started() 方法
listeners.started(context, timeTakenToStartup);
...
/**
* 发布应用监听启动事件
* @param context
*/
void started(ConfigurableApplicationContext context) {
// listener.started(context) 中交由context.publishEvent()方法处理
// 实际上是发送了一个ApplicationStartedEvent的事件
doWithListeners("spring.boot.application.started", (listener) -> listener.started(context));
}
18.执行自定义的run方法(可扩展)
callRunners,执行runner主要是遍历所有的runner获取所有的ApplicationRuner 和CommandLineRunner 来初始化参数,其中callRuner(是一个回调函数)。
这是一个扩展功能,callRunners(context, applicationArguments) 可以在启动完成后执行自定义的run方法;有2中方式可以实现:
- 实现 ApplicationRunner 接口
- 实现 CommandLineRunner 接口
...
// 18.执行runner,遍历所有的 runner,调用 run 方法
callRunners(context, applicationArguments);
...
/**
* 执行runner 初始化参数
* @param context
* @param args
*/
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
// 遍历所有runner
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
/**
* 回调函数callRunner 处理 ApplicationRunner
*/
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
/**
* 回调函数callRunner 处理 CommandLineRunner
*/
callRunner((CommandLineRunner) runner, args);
}
}
}
总结
SpringBoot启动流程总结就是下面两张图片,一个创建SpringApplication实例,一个执行run方法。
- 创建SpringApplication实例
- 执行run方法
参考:
9千字长文带你了解SpringBoot启动过程–史上最详细 SpringBoot启动流程-图文并茂
Spring Boot启动流程