上篇文章结束了prepareEnvironment方法的分析,本篇继续SpringApplication的run方法往下走,看一个比较简单的点——banner打印
所谓banner就是SpringBoot应用启动的时候打印在控制台的一个logo
涉及到的代码为下面这行printBanner
在这之前,先简单说下上面的这行configureIgnoreBeanInfo
private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
if (System.getProperty("spring.beaninfo.ignore") == null) {
Boolean ignore = (Boolean)environment.getProperty("spring.beaninfo.ignore", Boolean.class, Boolean.TRUE);
System.setProperty("spring.beaninfo.ignore", ignore.toString());
}
}
它将环境配置中的spring.beaninfo.ignore属性设置到系统变量中,如果没有配置,就默认为true
这个属性主要和內省机制有关,这里不详细展开,很多工具类比如BeanUtils底层都依赖了內省,spring.beaninfo.ignore设置为true,会忽略自定义的BeanInfo
至于为什么SpringBoot把这个属性默认设置为true,我个人的理解是很多底层的机制都依赖了內省,而自定义BeanInfo很可能干扰使用了內省机制的相关功能,所以SpringBoot不推荐使用,实际工作中也极少会做BeanInfo层面的定制
接下来进入正题,点开printBanner方法
private Banner printBanner(ConfigurableEnvironment environment) {
if (this.bannerMode == Mode.OFF) {
return null;
} 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);
}
}
bannerMode是banner打印的模式,取值有三个
public static enum Mode {
OFF,
CONSOLE,
LOG;
}
OFF就是不打印banner,CONSOLE是打印到标准输出流,会输出到控制台,LOG就是打印到日志文件
bannerMode在SpringApplication对象的构造函数里初始化为CONSOLE
public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
......
this.bannerMode = Mode.CONSOLE;
......
}
所以默认就是只打印到控制台的,但是一般我们项目都会集成一些日志框架,这些框架也可以把标准输出流的内容重定向到日志文件中
如果没有设置为关闭,就进入printBanner方法的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);
......
第一行定义了一个ResourceLoader,由于SpringApplication对象的resourceLoader并没有初始化,所以new了一个DefaultResourceLoader,这个东西我们已经见了很多次了
接下来新建了一个SpringApplicationBannerPrinter,入参是刚创建的DefaultResourceLoader和SpringApplication对象的banner属性,这个属性同样没有初始化,所以此时也是null
SpringApplicationBannerPrinter内部封装了将banner打印到不同渠道的方法,先看下它的成员变量
class SpringApplicationBannerPrinter {
static final String BANNER_LOCATION_PROPERTY = "spring.banner.location";
static final String BANNER_IMAGE_LOCATION_PROPERTY = "spring.banner.image.location";
static final String DEFAULT_BANNER_LOCATION = "banner.txt";
static final String[] IMAGE_EXTENSION = new String[]{"gif", "jpg", "png"};
private static final Banner DEFAULT_BANNER = new SpringBootBanner();
private final ResourceLoader resourceLoader;
private final Banner fallbackBanner;
banner可以是图片形式,也可以是文本形式,可供我们自定义配置,前两个成员变量就是自定义文本banner和图片banner的默认配置名,后续寻找banner的时候,先看有没有自定义,如果没有,再找默认的banner文件
第三个变量为默认的文本banner文件,即classpath下的banner.txt
第四个变量为默认的图片banner的后缀名,图片banner可以支持多种格式,即会到classpath下找图片banner.gif / jpg / png
最后根据bannerMode是LOG还是CONSOLE,调用SpringApplicationBannerPrinter对象不同的print方法,这两个print方法内部实现大致相同,只不过打印的目的地分别为log文件以及标准输出流System.out,我们这里就按照默认情况,看下打印到System.out的代码
public Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {
Banner banner = this.getBanner(environment);
banner.printBanner(environment, sourceClass, out);
return new SpringApplicationBannerPrinter.PrintedBanner(banner, sourceClass);
}
第一行先获取Banner对象
private Banner getBanner(Environment environment) {
SpringApplicationBannerPrinter.Banners banners = new SpringApplicationBannerPrinter.Banners();
banners.addIfNotNull(this.getImageBanner(environment));
banners.addIfNotNull(this.getTextBanner(environment));
if (banners.hasAtLeastOneBanner()) {
return banners;
} else {
return this.fallbackBanner != null ? this.fallbackBanner : DEFAULT_BANNER;
}
}
Banner对象就是对banner内容的封装,文本类型的banner会封装到ResourceBanner中,图片类型则封装到ImageBanner,而SpringBootBanner是默认的banner,我们下面会见到
先尝试获取图片类型的banner
private Banner getImageBanner(Environment environment) {
String location = environment.getProperty("spring.banner.image.location");
if (StringUtils.hasLength(location)) {
Resource resource = this.resourceLoader.getResource(location);
return resource.exists() ? new ImageBanner(resource) : 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;
}
}
到environment中查找配置spring.banner.image.location(SpringBoot中很多地方都是这样,虽然上面定义了静态变量,但是下面很多代码并没有引用),如果没有指定,就到classpath下看有没有默认的banner图片,也就是上面提到的在classpath下找图片banner.gif / jpg / png
接下来再找txt类型的banner
private Banner getTextBanner(Environment environment) {
String location = environment.getProperty("spring.banner.location", "banner.txt");
Resource resource = this.resourceLoader.getResource(location);
return resource.exists() ? new ResourceBanner(resource) : null;
}
先到environment看是否有配置spring.banner.location,如果没有,再默认找classpath下的banner.txt
如果图片类型和文本类型的banner都没有找到,就取fallbackBanner
return this.fallbackBanner != null ? this.fallbackBanner : DEFAULT_BANNER;
这个fallbackBanner传进来的是SpringApplication对象的banner属性,它并没有初始化,所以是null的,也就是说最终的fallbackBanner赋值为DEFAULT_BANNER,它是SpringApplicationBannerPrinter的静态变量,初始化为SpringBootBanner
class SpringApplicationBannerPrinter {
......
private static final Banner DEFAULT_BANNER = new SpringBootBanner();
......
看下这个SpringBootBanner 的实现
class SpringBootBanner implements Banner {
private static final String[] BANNER = new String[]{"", " . ____ _ __ _ _", " /\\\\ / ___'_ __ _ _(_)_ __ __ _ \\ \\ \\ \\", "( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\", " \\\\/ ___)| |_)| | | | | || (_| | ) ) ) )", " ' |____| .__|_| |_|_| |_\\__, | / / / /", " =========|_|==============|___/=/_/_/_/"};
private static final String SPRING_BOOT = " :: Spring Boot :: ";
private static final int STRAP_LINE_SIZE = 42;
SpringBootBanner() {
}
第一行String类型的数组,其实就是我们文章开头看到的SpringBoot默认的banner,它是以字符串的形式写死在代码里的
获取到banner后,调用它的print方法,最终通过System.out.println()将banner内容写到控制台
public void printBanner(Environment environment, Class<?> sourceClass, PrintStream out) {
try {
String banner = StreamUtils.copyToString(this.resource.getInputStream(), (Charset)environment.getProperty("spring.banner.charset", Charset.class, StandardCharsets.UTF_8));
PropertyResolver resolver;
for(Iterator var5 = this.getPropertyResolvers(environment, sourceClass).iterator(); var5.hasNext(); banner = resolver.resolvePlaceholders(banner)) {
resolver = (PropertyResolver)var5.next();
}
out.println(banner);
} catch (Exception var7) {
logger.warn("Banner not printable: " + this.resource + " (" + var7.getClass() + ": '" + var7.getMessage() + "')", var7);
}
}
很多公司都会在SpringBoot基础上做一层封装,然后加上自己公司的banner,这里附上一个自定义banner的网站
自定义banner
(https://patorjk.com/software/taag/#p=display&f=Doom&t=mybanner)
比如取名为MyBanner,然后将内同拷贝到classpath下的banner.txt
启动项目,默认banner已经被替换了