首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >「Spring源码分析」Banner

「Spring源码分析」Banner

原创
作者头像
花言不知梦
修改2020-05-25 10:32:45
1.5K0
修改2020-05-25 10:32:45
举报

今天主要来分析 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 -(转载请注明出处,如果喜欢,来动个小手点个赞,你的赞就是我写作的动力!)

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 3.2 输出 banner 内容
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档