原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9503210.html

一、概述

  这里是Springboot项目启动大概流程,区别于SSM框架项目启动流程。

二、启动流程

  Springboot项目可以直接从main方法启动。

  源码1-来自DemoApplicaiton(应用自定义的)

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

  首先调用的是SpringApplication中的run方法。

  源码2-来自:SpringApplication

     public static ConfigurableApplicationContext run(Class<?> primarySource,
String... args) {
return run(new Class<?>[] { primarySource }, args);
} public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}

  第一个run方法会调用第二个run方法。

  在第二个run方法中有两步:

    第一步:创建SpringApplicaiton实例

    第二步:调用run方法

  首先我们先创建SpringApplication实例

  源码3-来自:SpringApplication

     public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
} public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//判断应用类型
this.webApplicationType = deduceWebApplicationType();
//添加初始化器
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//添加监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//定位main方法所在类,并做记录
this.mainApplicationClass = deduceMainApplicationClass();
}

  SpringBoot应用有三种类型:

  源码4-来自:WebApplicationType

 public enum WebApplicationType {

     NONE,SERVLET,REACTIVE

 }
  •   NONE:应用不是web应用,启动时不必启动内置的服务器程序
  •   SERVLET:这是一个基于Servlet的web应用,需要开启一个内置的Servlet容器(web服务器)
  •   REACTIVE:这是一个反应式web应用,需要开启一个内置的反应式web服务器

  三者的判断条件如下

  源码5-来自:SpringApplication

     private WebApplicationType deduceWebApplicationType() {
if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_WEB_ENVIRONMENT_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}

  上面的方法中涉及四个常量:

  源码6-来自:SpringApplication

     private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework."
+ "web.reactive.DispatcherHandler"; private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework."
+ "web.servlet.DispatcherServlet"; private static final String JERSEY_WEB_ENVIRONMENT_CLASS = "org.glassfish.jersey.server.ResourceConfig"; private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };

  至于代码很是简单。

  然后是设置初始化器,结合之前的讲述在SSM架构的web应用中也拥有初始化器,这两个代表的是同一个概念,不同于之前的处理方式,这里仅仅是将初始化器找到并设置到initializers属性中。

  我们来看看它是如何获取这些初始化器的:

  源码7-来自:SpringApplication、SpringFactoriesLoader

     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
//使用指定的类加载器获取指定类型的类名集合
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//创建实例
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
//排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
} 下面来自:SpringFactoriesLoader public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
} private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
//首先尝试从缓存获取
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
} try
//从META-INF/spring.factories文件中获取配置的内容
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
List<String> factoryClassNames = Arrays.asList(
StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
result.addAll((String) entry.getKey(), factoryClassNames);
}
}
//添加到缓存备用
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}

  由此可见,初始化器是从META-INF/spring.factories文件中获取到的,那么我们就可以自建该目录文件进行添加,前提是需要自定义初始化器。

  还有这里只有在第一次调用时需要从文件读取,第二次就可以从缓存获取,哪怕需要的不是初始化器的内容,因为第一次的时候就会将所有的配置内容全部获取并解析保存到缓存备用。这也就为下一步设置监听器这一步省下了不少时间。

  最后一步就是将main方法所在类做个记录。

  源码8-来自:SpringApplication

     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;
}

  通过新建一个运行时异常的方式获取方法调用栈,在栈中搜索方法名为main的方法所在的类。

  如果我们想要获取方法的调用栈也可以采用这种方式,使用异常来获取调用栈。

  完成SpringApplication实例的创建之后,直接调用其run方法,执行应用启动。

  源码9-来自:SpringApplication

     public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//配置系统属性headless,默认为true
configureHeadlessProperty();
//获取所有的运行时监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();//启动监听器
try {
//封装自定义参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//根据参数配置环境environment
//如果是web项目则创建的是StandardServletEnvironment,否则是StandardEnvironment
//然后配置属性和profile
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
//设置是否忽略BeanInfo,默认为true
configureIgnoreBeanInfo(environment);
//打印banner
Banner printedBanner = printBanner(environment);
//创建ApplicationContext
//如果是Servlet web项目则创建AnnotationConfigEmbeddedWebApplicationContext,如果是Reactive web
//项目则创建AnnotationConfigReactiveWebServerApplicationContext,否则AnnotationConfigApplicationContext
context = createApplicationContext();
//加载异常报告器
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//筹备上下文环境,也就是初始化
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//刷新上下文
refreshContext(context);
//刷新后操作,这是两个孔方法,是可以自定义实现逻辑的
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);//容器和应用启用之后,callRunner之前执行
//容器启动完成之后回调runner,包括:ApplicationRunner和CommandLineRunner
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
} try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}

  这是整个容器启动的所有逻辑入口,参照注释即可理解,我们重点关注prepareContext方法,这个方法是在容器刷新之前的预初始化操作:

  源码10-来自:SpringApplication

     private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
//设置environment到上下文
context.setEnvironment(environment);
//设置BeanName生成器
//设置资源加载器或者类加载器
postProcessApplicationContext(context);
//执行初始化器
applyInitializers(context);
listeners.contextPrepared(context);//执行监听器的contextPrepared操作
//配置日志信息
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
} // Add boot specific singleton beans
//注册单例(springApplicationArguments,springBootBanner)
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
if (printedBanner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
} // Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
//加载Bean到容器
load(context, sources.toArray(new Object[0]));
//执行监听器的contextLoaded操作
listeners.contextLoaded(context);
}

  重点关注下load方法,他的目的是加载Bean定义:

  源码11-来自:SpringApplication

     //为load操作做准备,创建资源读取器扫描器
protected void load(ApplicationContext context, Object[] sources) {
if (logger.isDebugEnabled()) {
logger.debug(
"Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
}
//创建BeanDefinition读取器,扫描器(AnnotatedBeanDefinitionReader,XmlBeanDefinitionReader,
//GroovyBeanDefinitionReader,ClassPathBeanDefinitionScanner)
BeanDefinitionLoader loader = createBeanDefinitionLoader(
getBeanDefinitionRegistry(context), sources);
//配置BeanName生成器,资源加载器,环境参数到读取器和扫描器中
if (this.beanNameGenerator != null) {
loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader != null) {
loader.setResourceLoader(this.resourceLoader);
}
if (this.environment != null) {
loader.setEnvironment(this.environment);
}
//执行Bean的加载
loader.load();
} //统计加载Bean的数量
public int load() {
int count = 0;
for (Object source : this.sources) {
count += load(source);
}
return count;
} //针对不同的情况加载bean资源
private int load(Object source) {
Assert.notNull(source, "Source must not be null");
//
if (source instanceof Class<?>) {
return load((Class<?>) source);
}
//Resource资源可能是XML定义的Bean资源或者groovy定义的Bean资源
if (source instanceof Resource) {
return load((Resource) source);
}
//Packet资源一般是用于注解定义的Bean资源,需要扫描器扫描包
if (source instanceof Package) {
return load((Package) source);
}
//
if (source instanceof CharSequence) {
return load((CharSequence) source);
}
throw new IllegalArgumentException("Invalid source type " + source.getClass());
}

  针对不同的资源进行加载。

  下面就要回到run方法中的重点操作refreshContext(context)了:

  源码12-来自:SpringApplication

     private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
} protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext) applicationContext).refresh();
}

  这里也到了执行refresh()方法的时候了,下面我们就要看看这个refresh了。

Spring基础系列-容器启动流程(2)的更多相关文章

  1. Spring基础系列-容器启动流程(1)

    原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9870339.html 概述 ​ 我说的容器启动流程涉及两种情况,SSM开发模式和Spri ...

  2. Spring基础系列-AOP源码分析

    原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9560803.html 一.概述 Spring的两大特性:IOC和AOP. AOP是面向切 ...

  3. Spring基础系列--AOP织入逻辑跟踪

    原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9619910.html 其实在之前的源码解读里面,关于织入的部分并没有说清楚,那些前置.后 ...

  4. Spring基础系列-Spring事务不生效的问题与循环依赖问题

    原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9476550.html 一.提出问题 不知道你是否遇到过这样的情况,在ssm框架中开发we ...

  5. Spring基础系列-Web开发

    原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9996902.html SpringBoot基础系列-web开发 概述 web开发就是集成 ...

  6. spring boot, 容器启动后执行某操作

    常有在spring容器启动后执行某些操作的需求,现做了一个demo的实现,做一下记录,也希望可以给需要的同学提供参考. 1.spring启动后,以新线程执行后续需要的操作,所以执行类实现Runnabl ...

  7. Spring Boot 应用程序启动流程分析

    SpringBoot 有两个关键元素: @SpringBootApplicationSpringApplication 以及 run() 方法 SpringApplication 这个类应该算是 Sp ...

  8. SpringBean容器启动流程+Bean的生命周期【附源码】

    如果对SpringIoc与Aop的源码感兴趣,可以访问参考:https://javadoop.com/,十分详细. 目录 Spring容器的启动全流程 Spring容器关闭流程 Bean 的生命周期 ...

  9. dubbo源码学习(一)dubbo容器启动流程简略分析

    最近在学习dubbo,dubbo的使用感觉非常的简单,方便,基于Spring的容器加载配置文件就能直接搭建起dubbo,之前学习中没有养成记笔记的习惯,时间一久就容易忘记,后期的复习又需要话费较长的时 ...

随机推荐

  1. Android缩放动画[ScaleAnimation]

    @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); s ...

  2. python之路(七)-递归算法

    递归 特点 递归算法是一种直接或者间接地调用自身算法的过程.在计算机编写程序中,递归算法对解决一大类问题是十分有效的,它往往使算法的描述简洁而且易于理解. 递归算法解决问题的特点: (1) 递归就是在 ...

  3. CSS3背景相关新增属性

    background-clip border-box:充满边框和内边距,内容. padding-box:充满内边距,内容 content-box:只充满内容 background-origin bor ...

  4. 用clock()函数计时的坑

    程序中经常用time()函数来返回当前系统时间的秒数,来计时或计算时间差.如果需要用到更高精度的时间,就会自然想到用clock()函数.想当然的认为它返回从程序开始tick数,用clock()/CLO ...

  5. 网络操作系统 第七章 管理TCP/IP网络

    本章小结 本章介绍了TCP/IP的相关概念,并且在此处基础上,介绍了Windows Server 2008中使用TCP/IP网络配置工具实现网络连接和管理的方法,在Linux系统中,讲解了是如何使用图 ...

  6. InetAddress and InetSocketAddress

    1.InetAddress(包含IP地址及主机名) InetAddress is = InetAddress.getLocalHost(); /* 通过静态方法获得本机的对象 */ System.ou ...

  7. Redis-02.数据类型

    Redis中所有数据都是以key-value存储的,value支持的数据类型包括string.hash.list.set.sorted_set 数据类型 string 是redis最基本的类型,一个k ...

  8. 利用Module模块把构建的神经网络跑起来

    训练一个神经网络往往只需要简单的几步: 准备训练数据 初始化模型的参数 模型向往计算与向后计算 更新模型参数 设置相关的checkpoint 如果上述的每个步骤都需要我们写Python的代码去一步步实 ...

  9. FFmpeg开发实战(二):FFmpeg 文件操作

    FFmpeg 提供了丰富的API供我们使用,下面我们来讲述一下文件操作相关的API: FFmpeg 删除文件:avpriv_io_delete() FFmpeg 重命名文件:avpriv_io_mov ...

  10. 腾讯优秀 SDK 汇总

    1. 热修复 -- Tinker 项目地址:http://www.tinkerpatch.com/ SDK地址:https://github.com/Tencent/tinker 集成参考文档: ht ...