相信使用过Springboot的朋友们肯定对于这个图不陌生,这就是Springboot服务启动时候的默认的启动Banner。
作为爱倒腾的程序员,肯定不满足一成不变的这个图,自定义是必须的,例如打印个“LOVE”,或者把你女朋友的照片打印出来。于是乎,就要弄懂Banner打印的原理以及如何自定义打印的Banner图。
一、自定义Banner
先来看看效果打印一个"I LOVE JAVA"的ASCII图形的效果,如下图
在Springboot中打印实现这样子的一个效果非常简单,只需要在类路径下(Springboot的resources文件夹下)放置banner.txt文件,文件中的内容放置这个图形的ASCII文本内容即可。
那么这样子的ASCII内容如何制作呢,其实有很多第三方的网站提供了工具,例如
https://www.bootschool.net/ascii,你可以到网站上轻松的制作并下载,可以选择各种类型的字体。
除了已经制作好的ASCII文本内容,Springboot还支持gif、jpg、png格式的图像,同样命名为banner.gif/jpg/png放置在resources文件夹下即可。
第二种实现自定义banner的选择是设置spring.banner.location属性指定要打印的文本文件的位置,或者使用
spring.banner.image.location指定图形文件的位置,如果文件所使用的不是UTF-8的编码,你还可以通过spring.banner.charset设置指定的文件编码。
注意:banner.gif/jpg/png的优先级高于banner.txt。
我们尝试将百度的Logo以banner.png的形式放置在resources目录下,重新启动应用,可以看到打印出了百度Logo的ASCII图形。
二、Banner打印的原理
根据以上过程的操作可以看到Springboot应用启动的过程中会根据设置的Banner图片或者ASCII图形进行解析,根据解析出来的图片像素点对应不同的字符,将ASCII字符打印出来。
我们追踪SpringApplicaiton.run()方法,可以看到以下的代码:
public ConfigurableApplicationContext run(String... args) { ...... Banner printedBanner = printBanner(environment); ...... } private Banner printBanner(ConfigurableEnvironment environment) { if (this.bannerMode == Banner.Mode.OFF) { return 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); }
可以看到打印的功能主要由
SpringApplicationBannerPrinter.print()方法实现,我们一起来看看这个方法(以System.out分支为例):
Banner print(Environment environment, Class<?> sourceClass, PrintStream out) { Banner banner = getBanner(environment); banner.printBanner(environment, sourceClass, out); return new PrintedBanner(banner, sourceClass); } private Banner getBanner(Environment environment) { Banners banners = new Banners(); banners.addIfNotNull(getImageBanner(environment)); banners.addIfNotNull(getTextBanner(environment)); if (banners.hasAtLeastOneBanner()) { return banners; } if (this.fallbackBanner != null) { return this.fallbackBanner; } return DEFAULT_BANNER; }
可以看到,实际上Springboot提供了集中不同的Banner实现,有ImageBanner、TextBanner和自定义的fallbackBanner,会根据实际的情况选择不同的Banner实现进行Banner的打印。
以ImageBanner为例,跟踪发现其最终打印的实现代码如下:
private void printBanner(Environment environment, PrintStream out) throws IOException { int width = getProperty(environment, "width", Integer.class, 76); int height = getProperty(environment, "height", Integer.class, 0); int margin = getProperty(environment, "margin", Integer.class, 2); boolean invert = getProperty(environment, "invert", Boolean.class, false); BitDepth bitDepth = getBitDepthProperty(environment); PixelMode pixelMode = getPixelModeProperty(environment); Frame[] frames = readFrames(width, height); for (int i = 0; i < frames.length; i++) { if (i > 0) { resetCursor(frames[i - 1].getImage(), out); } printBanner(frames[i].getImage(), margin, invert, bitDepth, pixelMode, out); sleep(frames[i].getDelayTime()); } } private void printBanner(BufferedImage image, int margin, boolean invert, BitDepth bitDepth, PixelMode pixelMode, PrintStream out) { AnsiElement background = invert ? AnsiBackground.BLACK : AnsiBackground.DEFAULT; out.print(AnsiOutput.encode(AnsiColor.DEFAULT)); out.print(AnsiOutput.encode(background)); out.println(); out.println(); AnsiElement lastColor = AnsiColor.DEFAULT; AnsiColors colors = new AnsiColors(bitDepth); for (int y = 0; y < image.getHeight(); y++) { for (int i = 0; i < margin; i++) { out.print(" "); } for (int x = 0; x < image.getWidth(); x++) { Color color = new Color(image.getRGB(x, y), false); AnsiElement ansiColor = colors.findClosest(color); if (ansiColor != lastColor) { out.print(AnsiOutput.encode(ansiColor)); lastColor = ansiColor; } out.print(getAsciiPixel(color, invert, pixelMode)); } out.println(); } out.print(AnsiOutput.encode(AnsiColor.DEFAULT)); out.print(AnsiOutput.encode(AnsiBackground.DEFAULT)); out.println(); } public enum PixelMode { /** * Use text chars for pixels. */ TEXT(' ', '.', '*', ':', 'o', '&', '8', '#', '@'), /** * Use unicode block chars for pixels. */ BLOCK(' ', '\u2591', '\u2592', '\u2593', '\u2588'); private char[] pixels; PixelMode(char... pixels) { this.pixels = pixels; } char[] getPixels() { return this.pixels; } }
可以看到对于图片像素的解析过程以及对于打印出来的字符的配置,类似的可以自己实现自定义的Banner打印类,根据自己的需要设置打印不同的字符,通过
SpringApplicaiton.setBanner(Banner banner)来使用自定义的Banner。