大数据、Java EE 学习资料请关注 B 站:https://space.bilibili.com/204792350

通过阅读 SpringBoot 2.x 源代码而产生的粗糙版原理

流程

SpringApplication 对象

SpringApplication 对象在构造函数中,作了以下的初始化操作

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    // 资源加载器
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    // 将启动主程序类作为 LinkHashSet 的第一个元素,以便后续待用
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // 推断当前运行的项目是什么类型:非 Web、基于 Servlet API 的 Web、Reactive 的 Web
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}
  • getSpringFactoriesInstances:这个方法中,加载 Spring 的工厂实例。

应用程序类型

对当前的应用程序进行推断,有三种类型:

  • Reactive 的 Web 程序
    • 存在:org.springframework.web.reactive.DispatcherHandler
    • 不存在:org.springframework.web.servlet.DispatcherServlet、org.glassfish.jersey.servlet.ServletContainer
  • 基于 Servlet 的 Web 程序
  • 标准程序,非 Web 程序
    • 不存在:javax.servlet.Servlet、org.springframework.web.context.ConfigurableWebApplicationContext

这里不同程序需要满足不同的条件;

Servlet 是最后判断的,所以,在前面两种条件都不满足的情况就是 Servlet;

实例化所有 Spring 初始化器

首先,会尝试的将当前接收的类加载 ClassLoader 作为 key 从 cache 中获取是否已经加载过 SpringFactories 相关内容了,一旦存在数据,则会直接返回。

在上面的条件不满足的时候,接着 SpringBoot 会使用 AppClassLoader 类加载,调用 getResources 方法从类路径下加载 META-INF/spring.factories 文件,包含其它依赖包下的。

在这方法中有两种判断:

  • 一、通过类加载器,直接从类路径下搜索
  • 二、从用于加载类的搜索路径中查找指定名称的所有资源

不管哪种方法,都会将获取的 URLs 遍历,通过 UrlResource 类解析 file: 协议的路径,获取路径所在的文件。

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
   MultiValueMap<String, String> result = cache.get(classLoader);
   if (result != null) {
      return result;
   }

   try {
      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()) {
            String factoryTypeName = ((String) entry.getKey()).trim();
            for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
               result.add(factoryTypeName, factoryImplementationName.trim());
            }
         }
      }
      cache.put(classLoader, result);
      return result;
   }
   catch (IOException ex) {
      throw new IllegalArgumentException("Unable to load factories from location [" +
            FACTORIES_RESOURCE_LOCATION + "]", ex);
   }
}

将 resource 转为一个 Properties 对象之后,可以很方便的从其中获取相应的值。例如上面,从 Properties 中将所有的键值对数据遍历之后将其添加到 cache 中,以当前 ClassLoader 为 key。

根据 ApplicationContextInitializer.class 全类名,获取到所有的上下文类。

Snipaste_2020-05-30_13-55-40

紧接着将所有的 contet 相关的类,都实例化,并且按照注解标注的优先级排序,之后获取所有的 Spring 工厂的实例就到此为止。

返回的实例化工厂对象会被赋值到 SpringApplication对象的 this.initializers 属性。

Snipaste_2020-05-30_14-01-24

实例化监听器

与上面实例化 工厂基本一样的逻辑,区别在于这次是从 cache 缓冲中获取的 ApplicationListener.class 全类名的所有监听器。

Snipaste_2020-05-30_14-08-53

再将其排序之后,赋值到 SpringApplicatinthis.listeners 属性

public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
   this.listeners = new ArrayList<>(listeners);
}

设置 Main 主类

通过在堆栈跟踪中,获取名为 main 方法的全类名,然后将其通过反射方式实例化,并且赋值给 SpringApplication 的 this.mainApplicationClass 属性:

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

以上就是实例化 SpringApplication 的初始化操作。

运行 SpringApplication run 方法

开启计时监视

开始就是开启程序停止 watch,作用是对项目的启动时间或者其它计时。

Spring 应用程序运行监听器

这里的 listener 与之前在 SpringApplication 不同,在这里不是运行哪些监听器,而是运行一个事件发布监听器。

是什么呢?

EventPublishingRunListenner 类实现 SpringApplicationRunListener 接口,主要用于 SprintApplication 的 run 方法,每次 SpringBoot 启动的时候都会执行该事件侦听器。

跟 Servlet 的监听器不同,该类的监听器必须通过 META-INF/spring.factories 里面的 org.springframework.boot. SpringApplicationRunListener 作为将,然后实现了 SpringApplicationRunListener 接口的类作为值,可以多个,逗号为界。

具体是什么作用呢?

上面提到一点,每次 SpringBoot 运行都会回调这些侦听器,就是遍历,然后调用 starting 方法。

拿现成的侦听器来说,EventPublishingRunListenner 在 starting 里面调用 SimpleApplicationEventMulticaster 类,这个类间接实现底层 ApplicationEventMulticaster 接口,所起本质上看来,就是 SimpleApplicationEventMulticaster 的代理类,调用其 multicastEvent 方法,实例化 ApplicationStartingEvent 类,作为参数传入。

看其源代码,发现好像对日志方面的处理。

需要注意一点,事件的来源是 SpringApplication,这个实例在后期可能被修改。

看到这里了,发现好多接口,以及类;这些类与接口在后面的扩展中有一个详细的表格。

Snipaste_2020-05-30_15-13-32

处理环境参数

将 main 的 args 来自命令行的参数进行包装,转换为一个对象 DefaultApplicationArguments。

调用 SpringApplication StandardEnvironment 方法:

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
      ApplicationArguments applicationArguments) {
   // Create and configure the environment
   ConfigurableEnvironment environment = getOrCreateEnvironment();
   configureEnvironment(environment, applicationArguments.getSourceArgs());
   ConfigurationPropertySources.attach(environment);
   listeners.environmentPrepared(environment);
   bindToSpringApplication(environment);
   if (!this.isCustomEnvironment) {
      environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
            deduceEnvironmentClass());
   }
   ConfigurationPropertySources.attach(environment);
   return environment;
}

获取环境

通过 getOrCreateEnvironment 获取环境,有四种类型,在四种选一种,类型实现 ConfigurableWebEnvironment 接口

  • 自定义类型,也就是通过侦听器 starting 的方法来修改 SpringApplication 的 environment属性。
  • SERVLET
  • REACTIVE
  • 标准类型,非 Web 类型

添加服务转换器

通过这方法 configureEnvironment 来配置环境信息,这一步在环境信息里面添加了 ApplicationConversionService 服务转换器。

public static void addDefaultConverters(ConverterRegistry converterRegistry) {
    addScalarConverters(converterRegistry);
    addCollectionConverters(converterRegistry);

    converterRegistry.addConverter(new ByteBufferConverter((ConversionService) converterRegistry));
    converterRegistry.addConverter(new StringToTimeZoneConverter());
    converterRegistry.addConverter(new ZoneIdToTimeZoneConverter());
    converterRegistry.addConverter(new ZonedDateTimeToCalendarConverter());

    converterRegistry.addConverter(new ObjectToObjectConverter());
    converterRegistry.addConverter(new IdToEntityConverter((ConversionService) converterRegistry));
    converterRegistry.addConverter(new FallbackObjectToStringConverter());
    converterRegistry.addConverter(new ObjectToOptionalConverter((ConversionService) converterRegistry));
}
public static void addDefaultFormatters(FormatterRegistry formatterRegistry) {
    // Default handling of number values
    formatterRegistry.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());

    // Default handling of monetary values
    if (jsr354Present) {
        formatterRegistry.addFormatter(new CurrencyUnitFormatter());
        formatterRegistry.addFormatter(new MonetaryAmountFormatter());
        formatterRegistry.addFormatterForFieldAnnotation(new Jsr354NumberFormatAnnotationFormatterFactory());
    }

    // Default handling of date-time values

    // just handling JSR-310 specific date and time types
    new DateTimeFormatterRegistrar().registerFormatters(formatterRegistry);

    if (jodaTimePresent) {
        // handles Joda-specific types as well as Date, Calendar, Long
        new JodaTimeFormatterRegistrar().registerFormatters(formatterRegistry);
    }
    else {
        // regular DateFormat-based Date, Calendar, Long converters
        new DateFormatterRegistrar().registerFormatters(formatterRegistry);
    }
}

通过一步步追踪代码,发现在这个方法中,注册了一些默认的服务转换器,有什么有用呢?

比如说,在 controller 的方法形参里面,明明参数有时候前端请求发送的数据是字符串,但若是用 Integer 接收参数也是可以的。这一点就是 String -》Ingeter 的转换器的作用。

上面两个方法,将一些默认的转换器自动添加到环境中。

  • 默认转换服务
    • 默认转换器
    • 默认格式器
  • 应用程序转换服务
    • 应用程序格式器
    • 应用程序转换器

应用程序格式器

Snipaste_2020-05-30_16-54-20

应用程序转换器

Snipaste_2020-05-30_16-54-07

默认的一共有 124 个转换服务,图片只是一部分

Snipaste_2020-05-30_16-48-07

处理 Profile

同样在这方法中,也处理了 Profile 的环境配置;设置了激活的 Profile 配置文件。

protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
   if (this.addConversionService) {
      ConversionService conversionService = ApplicationConversionService.getSharedInstance();
      environment.setConversionService((ConfigurableConversionService) conversionService);
   }
   configurePropertySources(environment, args);
   configureProfiles(environment, args);
}

调用事件侦听器的 environmentPrepared 方法

代码太复杂,没继续跟踪,大概就是执行环境装备的处理。

配置忽略Bean信息

打印 Banner

就是 SpringBoot 启动的那个拼出来的图形;

Snipaste_2020-05-30_17-10-32

创建 IOC 容器

有三种 ioc 容器类型:

  • SERVLET
    • org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
  • REACTIVE
    • org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext
  • 标准程序,非 Web 程序
    • org.springframework.context.annotation.AnnotationConfigApplicationContext

分别带别不同的 ApplicationContext 类,之后使用 bean 的工具包将其实例化转换为 ConfigurableApplicationContext 类型返回。

异常分析报告

就是这行代码,在出现异常的时候;

exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
      new Class[] { ConfigurableApplicationContext.class }, context);

Snipaste_2020-05-30_17-18-56

老方式获取;

准备 IOC 容器

对容器设置一些数据;

context, environment, listeners, applicationArguments, printedBanner

在这个阶段,将之前准备好的环境数据,事件侦听器,命令行参数,Banner 输出,对 ioc 进行绑定阶段。

设置 ioc 环境数据

context.setEnvironment(environment)

后置处理 context ioc 容器相关设置

postProcessApplicationContext(context)

在 ioc 后置处理方法中,会对 ioc 容器进行赋值设置,将内部 bean 名称自动生成器、资源加载器、服务转换。

执行所有初始化器

对之前后期到的上下文初始化器遍历执行,调用其中的:

initializer.initialize(context);

这些初始化器都是单例模式。

Snipaste_2020-05-30_14-01-24

回调 SpringApplicationRunListener 侦听器

这是调用侦听器的 contextPrepared 方法,ioc 容器作为参数。

启动日志记录

根据 SpringApplication 的属性 logStartupInfo 来决定是否开启日志记录功能,默认是开启的;

想要修改,只能编写 SpringApplicationRunListener 这里边的监听器来初始化 SpringApplication 的数据;

获取 Bean 工厂

返回 SpringApplicaiton 的 beanFactory 属性,这是默认的工厂类:DefaultListableBeanFactory

注册 DefaultApplicationArguments

将这个 DefaultApplicationArguments 实例作为单例注册到 bean 中,默认的 bean 名称:springApplicationArguments

注册 Banner

将 SpringApplicationBannerPrinter 注册到 bean 里边,单例模式,同时默认名称:springBootBanner

设置 Bean 工厂

设置两个参数:

  • 允许 Bean 覆盖
    • 这个可能不会生效,满足条件必须是:DefaultListableBeanFactory 的子类或者当前实例;
  • 懒惰加载

回调 contextLoaded

表示加载完成,然后调用 SpringApplicationRunListener 侦听器,回调所有的监听器的 contextLoaded 方法;

刷新 IOC 容器

简单说就是对容器进行初始化,这个阶段会加载所有组件,bean,启动 Web 服务器等等;

以下的所有代码,就是这个阶段需要完成大绝大部分流程;

@Override
public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
       // 准备初始化上下文
       prepareRefresh();
       // 告诉子类刷新内部bean工厂
       ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
       // 准备bean工厂,以便在此上下文中使用
       prepareBeanFactory(beanFactory);
       
       try {
           // 允许在上下文子类中对bean工厂进行后置处理
           postProcessBeanFactory(beanFactory);
			// 调用上下文中注册为bean的工厂后置处理器
           invokeBeanFactoryPostProcessors(beanFactory);
           // 注册拦截bean创建的bean处理器。
           registerBeanPostProcessors(beanFactory);
           // 初始化此上下文的消息源
           initMessageSource();
           // 为此上下文初始化 EventMulticaster
           initApplicationEventMulticaster();
           // 初始化特定上下文子类中的其他特殊bean
           // 例如:启动 Web 服务器
           onRefresh();
           // 检查侦听器bean并注册它们
           registerListeners();
           // 实例化所有剩余的(非lazy-init)单例。
           finishBeanFactoryInitialization(beanFactory);
           // 最后一步:发布相应的事件。
           finishRefresh();
       } catch (BeansException ex) {
           if (logger.isWarnEnabled()) {
               logger.warn("Exception encountered during context initialization - " +
                           "cancelling refresh attempt: " + ex);
           }
           // 销毁已经创建的单例,以避免挂起资源。
           destroyBeans();
           // 重置 active flag
           cancelRefresh(ex);
			// 将异常传播给调用者
           throw ex;
       } finally {
           // 重置Spring核心的公共内省缓存,因为我们可能不再需要单例bean的元数据了……
           resetCommonCaches();
       }
   }
}

IOC 刷新后处理

这里是一个空方法,SpringBoot 2.x 将 callRunners 移出来,单独作为一个阶段调用;

停止监视器

到了这一阶段 ioc 容器初始化,加载,启动完成了,所以时间记录器到这里关闭。

启动日志记录器

这个是在 SpringBoot 启动的时候,在控制台输出的日志信息;

监听器启动完成阶段回调

遍历所有 ApplicationListener 监听器,调用每一个监听器的 started 方法,ioc 容器作为参数;

回调运行

从 ioc 容器中,获取以下两种类型 bean:

  • ApplicationRunner
  • CommandLineRunner

对它们根据注解优先级排序之后,遍历回调 run 方法,applicationArguments 作为参数;

应用程序运行中阶段回调

同样回调所有 ApplicationListener 的监听器的 running 方法;

完成

返回 ioc 容器;

总结

  • 在最初的实例化 SpringApplication 的时候就对 SpringBoot 所有初始化器、监听器、程序类型推断等上下文环境的数据进行处理,同时将 META-INF/spring.factories 获取到工厂实例进行变量缓存;
  • 想要修改 SpringBoot 对象数据,需要在 META-INF/spring.factories 定义名为:org.springframework.context.ApplicationContextInitializer 作为键的格式,后面的值就是对应的需要初始化的初始化器全类名,在 SpringBoot prepareContext 阶段会加载所有初始化器;
  • 想要修改 SpringBoot 对象数据,需要在 META-INF/spring.factories 定义名为:org.springframework.boot.SpringApplicationRunListener 作为键的格式,后面的值就是对应的需要初始化的运行时监听器全类名,SpringApplicationRunListener 的事件来自于 SpringBoot 对象,而且这些是运行时的监听器的,与应用和程序监听器 ApplicationListener 不同。
  • 想要修改 SpringBoot 对象数据,需要在 META-INF/spring.factories 定义名为:org.springframework.context.ApplicationListener 作为键的格式,后面的值就是对应的需要初始化的监听器,在 SpringBoot 各个阶段都会调用其实现的阶段方法;
  • SpringBoot 启动时候会对启动时间进行记录
  • 启动的回调有几个阶段,这几个阶段的,所有监听器在需要的时候,会被回调;
    • 启动中:starting
    • 启动完成:started
    • 运行中:running
    • 失败:failed
    • 几个特殊的回调:environmentPreparedcontextPreparedcontextLoaded,分别是在环境准备阶段,上下文准备阶段、上下文加载阶段
  • META-INF/spring.factoies 加载 SpringApplicationRunListener 类型的监听器
  • Web 服务器的启动在 refreshContext 阶段的,这个阶段处理的是一些特殊的 bean
  • 在 prepareEnvironment 阶段设置了应用程序的环境数据,回调监听器的方法,绑定环境数据、配置文件 Profile等等一些与环境相关的设置;
  • 有四个类关于阶段性的回调:SpringApplicationRunListener、ApplicationContextInitializer、ApplicaitonRunner、CommandLineRunner
    • SpringApplicationRunListener:最先执行,分阶段执行,需要有一个有参构造器
    • ApplicationContextInitializer:其次执行,initialize,
    • ApplicaitonRunner、CommandLineRunner:前者优先级高,后者低;

扩展

事件侦听器

名称类型描述
SpringApplicationRunListeners运行 linstener 实例
EventPublishingRunListener使用内部 ApplicationEventMulticaster 来处理在实际刷新上下文之前触发的事件。
SpringApplicationRunListener接口用于 SpringApplication run 方法的侦听器。
SpringApplicationRunListener 是通过 SpringFactoriesLoader 加载的,应该声明一个公共构造函数,该构造函数接受一个 SpringApplication 实例和一个 String[] 参数。每次运行都将创建一个新的SpringApplicationRunListener 实例。
SimpleApplicationEventMulticaster简单的实现 ApplicationEventMulticaster 接口。
将所有事件广播给所有已注册的侦听器,让侦听器忽略它们不感兴趣的事件。
侦听器通常会对传入的事件对象执行相应的 instanceof 检查。
AbstractApplicationEventMulticaster抽象类抽象实现了 ApplicationEventMulticaster 接口,提供了基本的监听器注册功能。
默认情况下不允许同一个监听器有多个实例,因为它将监听器保持在一个链接集中。用于保存 ApplicationListener 对象的 collection 类可以通过 collectionClass bean 属性覆盖。
ApplicationEventMulticaster接口接口由可以管理许多 ApplicationListener 对象并向其发布事件的对象实现。一个 org.springframework.context。
通常是一个 Spring ApplicationEventPublisher,可以使用ApplicationEventMulticaster 作为实际发布事件的委托。
ApplicationStartingEvent在 Environment 或 ApplicationContext 可用之前,但在 ApplicationListener 注册之后,尽可能早地发布事件(只要启动了 SpringApplication)。
事件的来源是 SpringApplication 本身,但是要注意在这个早期阶段过多地使用它的内部状态,因为它可能在生命周期的后期被修改。
SpringApplicationEvent抽象类与一个 SpringApplication 相关的 ApplicationEvent的基类。
ApplicationEvent抽象类派生所有事件状态对象的根类。所有事件都是通过对对象(源)的引用构造的,该对象在逻辑上被认为是事件最初发生的对象。
EventObject派生所有事件状态对象的根类。
所有事件都是由对对象的引用构造而成,该对象即“源”,逻辑上被认为是事件最初发生时的对象。
# Java   SpringBoot  

评论

公众号:mumuser

企鹅群:932154986

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×