今天主要来分析 Spring banner 的加载流程,从获取内容和输出内容这两个角度进行分析
-> SpringApplication.run(DemoApplication.class, args)
-> run(new Class[]{primarySource}, args)
-> (new SpringApplication(primarySources)).run(args)
// 这里就是输出banner的入口
Banner printedBanner = 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);
}
}
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;
}
输出的时候,根据返回的 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 删除。