专栏首页花言不知梦「Spring源码分析」Banner
原创

「Spring源码分析」Banner

今天主要来分析 Spring banner 的加载流程,从获取内容输出内容这两个角度进行分析

  1. 找到 banner 加载的入口
-> SpringApplication.run(DemoApplication.class, args)
-> run(new Class[]{primarySource}, args)
-> (new SpringApplication(primarySources)).run(args)
   // 这里就是输出banner的入口
   Banner printedBanner = this.printBanner(environment)
  1. 进入 this.printBanner(environment) 方法

补充:关闭 banner有两种方式,第一种方式是在启动类中关闭,通过 springApplication.setBannerMode(Banner.Mode.OFF) 这一行代码,第二种方式是在 application.properties配置文件中,添加 spring.main.banner-mode = off 这一行配置

2. 进入 printBanner(environment) 方法
   该方法的作用是判断当前的banner模式是否是关闭状态,如果是,则不输出banner
   private Banner printBanner(ConfigurableEnvironment environment) {
        // 如果banner模式是关闭状态,则不输出banner
        if (this.bannerMode == Mode.OFF) {
            return null;
        } 
        /*
          判断是banner模式是控制台模式还是日志模式
          这两个模式,输出目的地不一样,但是其中的输出原理是一样的
        */
        else {
            ResourceLoader resourceLoader = this.resourceLoader != null ? this.resourceLoader : new DefaultResourceLoader(this.getClassLoader());
            SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter((ResourceLoader)resourceLoader, this.banner);
            return this.bannerMode == Mode.LOG ? bannerPrinter.print(environment, this.mainApplicationClass, logger) : bannerPrinter.print(environment, this.mainApplicationClass, System.out);
        }
    }
  1. 进入 bannerPrinter.print(environment,this.mainApplicationClass, System.out) 方法
3. bannerPrinter.print(environment, this.mainApplicationClass, System.out)
   该方法的作用是 获取banner内容 和 输出banner内容
   Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {
        // 3.1 获取banner内容
        Banner banner = this.getBanner(environment);
        // 3.2 输出banner内容
        // 根据返回的banner类型,输出不同的内容,这就是多态的运用!
        // 针对 SpringApplicationBannerPrinter.Banners类型,遍历banners列表,获取Banner类型的对象
 //依次调用ImageBanner类型或者ResourceBanner类型(这两个都是Banner类型、又一个多态!)的print方法
        // 针对 SpringBootBanner类型,默认的文本输出
        banner.printBanner(environment, sourceClass, out);
        return new SpringApplicationBannerPrinter.PrintedBanner(banner, sourceClass);
    }

3.1 获取 banner 内容( 获取的顺序依次为:图片banner -> 文本banner -> 兜底banner -> 默认banner )

针对图片banner,要么通过 spring.banner.image.location属性 指定加载 图片banner 的路径,或者在resources目录下存放 banner.gif 或 banner.jpg 或 banner.png 格式的 图片banner

针对文本banner,可以通过 spring.banner.location属性 指定加载文本banner 的路径,如果没有加载,Spring会尝试从resources目录下的 加载名为 ''banner.txt'' 的资源,如果没有,则返回

如果说 图片banner 和 文本banner 都没加载到,则去查看 兜底banner 是否存在,( 兜底banner 在启动类中手动加载,比如springApplication.setBanner(newResourceBanner(newClassPathResource("favorite.txt"))) 这行代码)上面三个banner都不存在的话,返回 默认banner

3.1 Banner banner = this.getBanner(environment)
   该方法的作用是获取banner内容(加载顺序是先图片banner,然后文本banner,最后兜底banner。如果都没有,则返回默认banner)
   private Banner getBanner(Environment environment) {
        SpringApplicationBannerPrinter.Banners banners = new SpringApplicationBannerPrinter.Banners();
        // 3.1.1 尝试加载图片banner
        // 注:addIfNotNull()方法表示当且仅当 banner不为null时,才会加载
        // 该方法实际上是添加到SpringApplicationBannerPrinter类的内部类Banners类维护的banners列表
        banners.addIfNotNull(this.getImageBanner(environment));
        // 3.1.2 尝试加载文本banner
        banners.addIfNotNull(this.getTextBanner(environment));
        // 如果banners列表中存在至少一个banner,直接返回banners
        if (banners.hasAtLeastOneBanner()) {
            return banners;
        } else {
            // 尝试加载兜底banner,如果失败,返回默认banner
            return this.fallbackBanner != null ? this.fallbackBanner : DEFAULT_BANNER;
        }
    }
    
3.1.1 this.getImageBanner(environment)
   该方法的作用是获取图片banner(指定方式或者默认方式)
   private Banner getImageBanner(Environment environment) {
        // 从 environment对象的属性源集合中获取 spring.banner.image.location对应的属性值
        // application.properties配置文件中的spring.banner.image.location属性指定banner图像的加载路径
        String location = environment.getProperty("spring.banner.image.location");
        
        // 1. 如果存在 spring.banner.image.location属性 所对应的属性值,则加载相应的资源
        if (StringUtils.hasLength(location)) {
            Resource resource = this.resourceLoader.getResource(location);
            return resource.exists() ? new ImageBanner(resource) : null;
        } 
        
        // 2. 如果不存在 spring.banner.image.location属性 所对应的属性值
        // 则会尝试从resources目录下依次加载banner.gif、banner.jpg、banner.png对应的资源,如果为空,返回null
        else {
            String[] var3 = IMAGE_EXTENSION;
            int var4 = var3.length;

            for(int var5 = 0; var5 < var4; ++var5) {
                String ext = var3[var5];
                Resource resource = this.resourceLoader.getResource("banner." + ext); // 加载资源
                if (resource.exists()) {
                    return new ImageBanner(resource);
                }
            }

            return null;
        }
    }
    
3.1.2 this.getTextBanner(environment)
   该方法的作用是获取文本banner(指定方式或者默认方式)
   private Banner getTextBanner(Environment environment) {
        // 获取application.yml文件中的 spring.banner.location属性 所对应的属性值。如果没有,则返回默认值banner.txt
        String location = environment.getProperty("spring.banner.location", "banner.txt");
        Resource resource = this.resourceLoader.getResource(location);
        return resource.exists() ? new ResourceBanner(resource) : null;
    }

3.2 输出 banner 内容

输出的时候,根据返回的 banner 类型,输出不同的内容,这就是多态的运用

针对 SpringApplicationBannerPrinter.Banners类型,遍历banners列表,获取Banner类型的对象,依次调用ImageBanner类型或者ResourceBanner类型(这两个都是Banner类型、又一个多态)的print方法;针对 SpringBootBanner类型,输出默认的文本

3.2.1 默认banner输出
   banner.printBanner(environment, sourceClass, out) -- SpringBootBanner类
   该方法的作用是输出 默认banner
   public void printBanner(Environment environment, Class<?> sourceClass, PrintStream printStream) {
        String[] var4 = BANNER;
        int var5 = var4.length;
        
        // 1. 输出文本内容(图案)
        for(int var6 = 0; var6 < var5; ++var6) {
            String line = var4[var6];
            printStream.println(line);
        } // 在控制台打印出Spring图案

        // 2. 获取Spring版本号
        String version = SpringBootVersion.getVersion(); // version = 2.2.5.RELEASE
        version = version != null ? " (v" + version + ")" : ""; // version =  (v2.2.5.RELEASE)
        
        // 3. 文本内容前后对齐
        StringBuilder padding = new StringBuilder();
        while(padding.length() < 42 - (version.length() + " :: Spring Boot :: ".length())) {
            padding.append(" ");
        }// padding = 6个空白符(42-(17+19))

        // 4. 文本内容染色
        printStream.println(AnsiOutput.toString(new Object[]{AnsiColor.GREEN, " :: Spring Boot :: ", AnsiColor.DEFAULT, padding.toString(), AnsiStyle.FAINT, version}));
        // 5. 输出文本内容(处理过的版本号)
        printStream.println(); // print-> :: Spring Boot ::        (v2.2.5.RELEASE)
    }

----
3.2.2 banners列表不为空的情况下
      输出图片banner 或者 文本banner,或者两者都输出
   printBanner(environment, sourceClass, out) -- SpringApplicationBannerPrinter.Banners类
   public void printBanner(Environment environment, Class<?> sourceClass, PrintStream out) {
            Iterator var4 = this.banners.iterator();
            // 遍历banners列表,在这里完成对图片banner和文本banner的输出
            while(var4.hasNext()) {
                Banner banner = (Banner)var4.next();
                // 该代码的输出根据图片banner和文本banner的类型而视,下面进一步阐述
                banner.printBanner(environment, sourceClass, out);
            }
   }
   
----
   补充:对(banner.printBanner(environment, sourceClass, out))的阐述
   图片banner输出
   printBanner(environment, sourceClass, out) -- ImageBanner类
   该方法的作用主要是进行无头模式的处理
   public void printBanner(Environment environment, Class<?> sourceClass, PrintStream out) {
        String headless = System.getProperty("java.awt.headless"); // headless = true

        try {
            // 设置程序处于Headless模式,在没有外设的情况下,依赖系统的计算能力来处理可视化等特征
            System.setProperty("java.awt.headless", "true");
            this.printBanner(environment, out);
        } catch (Throwable var9) {
            logger.warn(LogMessage.format("Image banner not printable: %s (%s: '%s')", this.image, var9.getClass(), var9.getMessage()));
            logger.debug("Image banner printing failure", var9);
        } finally {
            if (headless == null) {
                System.clearProperty("java.awt.headless");
            } else {
                System.setProperty("java.awt.headless", headless);
            }

        }

    }
   this.printBanner(environment, out)
   该方法的作用是是输出 图片banner
   private void printBanner(Environment environment, PrintStream out) throws IOException {
        // 1. 通过 spring.banner.image.* 获取图片的属性
        // 通过 spring.banner.image.width属性 获取图片的宽度,默认值是76
        int width = (Integer)this.getProperty(environment, "width", Integer.class, 76);
        // 通过 spring.banner.image.height属性 获取图片的高度,默认值是0
        int height = (Integer)this.getProperty(environment, "height", Integer.class, 0);
        // 通过 spring.banner.image.margin属性 获取图片的外边距,默认值是2
        int margin = (Integer)this.getProperty(environment, "margin", Integer.class, 2);
        // 通过 spring.banner.image.invert属性 判断图片的颜色样本是否反转输入,默认值是false
        boolean invert = (Boolean)this.getProperty(environment, "invert", Boolean.class, false);
        // 通过 spring.banner.image.bitdepth属性 获取图片的色深,默认值是4-bit(色深指的是在某一分辨率下,每一个像素点可以用多少色彩进行描述)
        BitDepth bitDepth = this.getBitDepthProperty(environment);
        // 通过 spring.banner.image.pixelmode属性 获取图片的像素点模式,默认值是ImageBanner.PixelMode.TEXT(new char[]{' ', '.', '*', ':', 'o', '&', '8', '#', '@'})
        // 其中,像素点模式指的是每一个像素点,都可以用哪些字符来替换
        ImageBanner.PixelMode pixelMode = this.getPixelModeProperty(environment);
        // 2. 读取图片文件流
        // 使用image属性加载绘制框架
        ImageBanner.Frame[] frames = this.readFrames(width, height);
        
        // 3. 输出图片内容(开始绘制图案)
        for(int i = 0; i < frames.length; ++i) {
            if (i > 0) {
                this.resetCursor(frames[i - 1].getImage(), out);
            }

            this.printBanner(frames[i].getImage(), margin, invert, bitDepth, pixelMode, out);
            this.sleep(frames[i].getDelayTime());
        }

    }

   文本banner输出
   banner.printBanner(environment, sourceClass, out) -- ResourceBanner类
   该方法的作用是输出文本banner
   public void printBanner(Environment environment, Class<?> sourceClass, PrintStream out) {
        try {
            // 1. 获取文本内容
            // 将指定的给定路径所加载的资源解析成字符串(通过spring.banner.charset指定字符集,默认是utf-8)
            String banner = StreamUtils.copyToString(this.resource.getInputStream(), (Charset)environment.getProperty("spring.banner.charset", Charset.class, StandardCharsets.UTF_8));

            // 2. 循环调用属性解析器,替换占位符
            PropertyResolver resolver;
            for(Iterator var5 = this.getPropertyResolvers(environment, sourceClass).iterator(); var5.hasNext(); banner = resolver.resolvePlaceholders(banner)) {
                resolver = (PropertyResolver)var5.next();
            }
            
            // 3. 输出文本banner
            out.println(banner);
        } catch (Exception var7) {
            logger.warn(LogMessage.format("Banner not printable: %s (%s: '%s')", this.resource, var7.getClass(), var7.getMessage()), var7);
        }

    }

散花~本章分析完结(୧(๑•̀◡•́๑)૭)

吃个热狗🌭先

- End -(转载请注明出处,如果喜欢,来动个小手点个赞,你的赞就是我写作的动力!)

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 「Spring 源码分析」Profile

    profile 定义了一组有逻辑关系的 bean定义,当且仅当 profile 被激活的时候,才会注入到容器当中。也就是说,程序只需要构建一次,就可以部署到多个...

    花言不知梦
  • 「Spring源码分析」Environment

    表示当前应用系统正在运行的环境,为 profiles 和 properties 这两个重要的方面提供模型,Environment接口定义了处理profiles的...

    花言不知梦
  • 「Spring 源码分析」Aware

    一个空标记接口,表示 bean 可以通过接口定义的回调方法,获取相应的 Spring容器对象 对bean进行处理

    花言不知梦
  • springboot 定制个性 banner

    使用spring boot 开发时,当程序启动的时候控制台会输出由字符组成的Spring符号。这个是SpringBoot为自己设计的Banner:

    似水的流年
  • SpringBoot基础之banner玩法解析

    SpringBoot项目启动时会在控制台打印一个默认的启动图案,这个图案就是我们要讲的banner。看似简单的banner,我们能够对它做些什么呢?本篇文章就带...

    用户1161110
  • ASP.NET Core Web程序托管到Windows 服务

    在 .NET Core 3.1和WorkerServices构建Windows服务 我们也看到了,如何将workerservices构建成服务,那么本篇文章我们...

    HueiFeng
  • ASP.NET Core Web程序托管到Windows 服务

    在 .NET Core 3.1和WorkerServices构建Windows服务 我们也看到了,如何将workerservices构建成服务,那么本篇文章我们...

    HueiFeng
  • 1.4 比特币的原理-账户所有权问题

    比特币系统里面如何验证某个比特币是谁的,谁拥有这个比特币。我们还是先对标一下银行系统来理解这个问题。

    Meet相识
  • springboot 定制个性 banner

    使用spring boot 开发时,当程序启动的时候控制台会输出由字符组成的Spring符号。这个是SpringBoot为自己设计的Banner:

    似水的流年
  • JDK7并行计算框架介绍二 Fork/Join开发实例

    package forktest; import java.util.*; import java.util.concurrent.RecursiveActio...

    数据饕餮

扫码关注云+社区

领取腾讯云代金券