掘金 后端 ( ) • 2024-04-17 22:24

highlight: atelier-cave-dark
theme: channing-cyan

前言

本文是作者写关于Spring源码的第一篇文章,作者水平有限,所有的源码文章仅限用作个人学习记录。文中如有错误欢迎各位留言指正。

run

public ConfigurableApplicationContext run(String... args) {
// 为了计时用的,老版本和新版本不一样
   long startTime = System.nanoTime();
   // 初始化一个引导器的上下文,这是属于Spring Boot的上下文。后边还有一个Spring的上下文。apach好喜欢context这个东西,证明写框架这个context是真的好用。
   DefaultBootstrapContext bootstrapContext = createBootstrapContext();
   // 这是Spring的上下文,在这里定义,在下面进行的初始化
   ConfigurableApplicationContext context = null;
   // 配置一个系统属性
   configureHeadlessProperty();
   // 获取配置文件的监听器 重点 也是扩展点,凡是读取配置文件的地方都是扩展点,因为配置在配置文件中的initializer、listener都会在某个阶段被调用
   SpringApplicationRunListeners listeners = getRunListeners(args);
   // 调用监听器发送启动事件,这里可以通过自定义监听器消费这个事件,处理自己的逻辑
   listeners.starting(bootstrapContext, this.mainApplicationClass);
   try {
   // 解析命令行参数 将其封装成一个ApplicationArguments,这个类的变量name被设置成commandLineArgs字符串,变量source是解析args封装的CommandLineArgs对象。
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      // 环境预处理
      ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
      // 配置忽略beanInfo
      configureIgnoreBeanInfo(environment);
      // 打印banner信息
      Banner printedBanner = printBanner(environment);
      context = createApplicationContext();
      context.setApplicationStartup(this.applicationStartup);
      prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
      refreshContext(context);
      afterRefresh(context, applicationArguments);
      Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
      }
      listeners.started(context, timeTakenToStartup);
      callRunners(context, applicationArguments);
   }
   catch (Throwable ex) {
      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;
}

printBanner

private Banner printBanner(ConfigurableEnvironment environment) {
判断banner的模式,一共有三种模式off、console、log,见下图一。默认是console
   if (this.bannerMode == Banner.Mode.OFF) {
      return null;
   }
   resourceLoader默认是null,构造函数中进行的赋值,函数的对应的参数就是null。
   ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader
         : new DefaultResourceLoader(null);
   SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);
   if (this.bannerMode == Mode.LOG) {
   日志文件打印
      return bannerPrinter.print(environment, this.mainApplicationClass, logger);
   }
   控制台打印
   return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}
  • 图一

image.png

print

Banner print(Environment environment, Class<?> sourceClass, Log logger) {
获取banner 这里面可以解密我们自定义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);
}

getBanner

private Banner getBanner(Environment environment) {
   Banners banners = new Banners();
   添加图片的banner
   banners.addIfNotNull(getImageBanner(environment));
   添加文本的banner
   banners.addIfNotNull(getTextBanner(environment));
   是否至少包含一个banner
   if (banners.hasAtLeastOneBanner()) {
   如果是直接返回banners
      return banners;
   }
   参数其实是null
   if (this.fallbackBanner != null) {
      return this.fallbackBanner;
   }
   默认banner 见下面的图一
   return DEFAULT_BANNER;
}

是不是很熟悉。

image.png

getImageBanner

  • 如果配置了spring.banner.image.location属性 直接读取这个属性的资源作为imagebanner
  • 如果没有配置上面的属性,直接在classpath下放置一个banner命名,"gif", "jpg", "png" 这些后缀的图片格式也可以被加载称为imagebanner
private Banner getImageBanner(Environment environment) {
获取spring.banner.image.location属性值,是从环境变量中获取的,那我们可以在环境变量中设置这个属性哟
   String location = environment.getProperty(BANNER_IMAGE_LOCATION_PROPERTY);
  
   if (StringUtils.hasLength(location)) {
   如果有这个属性就用资源加载器加载该资源
      Resource resource = this.resourceLoader.getResource(location);
      资源不为空 直接转成图片类型的banner
      return resource.exists() ? new ImageBanner(resource) : null;
   }
   for (String ext : IMAGE_EXTENSION) {
   可以加载这些后缀的banner图片{ "gif", "jpg", "png" }
      Resource resource = this.resourceLoader.getResource("banner." + ext);
      if (resource.exists()) {
         return new ImageBanner(resource);
      }
   }
   return null;
}

getTextBanner

private Banner getTextBanner(Environment environment) {
或者spring.banner.location这个环境变量属性值,所以这里我们也是可以配置属性来控制加载哪个banner,如果不存在默认值为banner.txt
   String location = environment.getProperty(BANNER_LOCATION_PROPERTY, DEFAULT_BANNER_LOCATION);
   加载类路径下的spring.banner.location资源或者直接加载banner.txt
   Resource resource = this.resourceLoader.getResource(location);
   try {
      if (resource.exists() && !resource.getURL().toExternalForm().contains("liquibase-core")) {
      存在就生成banner资源
         return new ResourceBanner(resource);
      }
   }
   catch (IOException ex) {
      // Ignore
   }
   return null;
}

createStringFromBanner

private String createStringFromBanner(Banner banner, Environment environment, Class<?> mainApplicationClass)
      throws UnsupportedEncodingException {
   ByteArrayOutputStream baos = new ByteArrayOutputStream();
   默认的话是SpringBootBanner的printBanner
   banner.printBanner(environment, mainApplicationClass, new PrintStream(baos));
   String charset = environment.getProperty("spring.banner.charset", "UTF-8");
   return baos.toString(charset);
}

printBanner

public void printBanner(Environment environment, Class<?> sourceClass, PrintStream printStream) {
   for (String line : BANNER) {
   banner信息保存到输入流
      printStream.println(line);
   }
   拼接版本信息
   String version = SpringBootVersion.getVersion();
   version = (version != null) ? " (v" + version + ")" : "";
   StringBuilder padding = new StringBuilder();
   while (padding.length() < STRAP_LINE_SIZE - (version.length() + SPRING_BOOT.length())) {
      padding.append(" ");
   }

版本信息保存到输入流
   printStream.println(AnsiOutput.toString(AnsiColor.GREEN, SPRING_BOOT, AnsiColor.DEFAULT, padding.toString(),
         AnsiStyle.FAINT, version));
换行
   printStream.println();
}