springboot源码分析[4]banner打印
springboot 启动时是如何打印banner的, 以及如何配置一个自己的banner.
入口方法
private Banner printBanner(ConfigurableEnvironment environment) {
if (this.bannerMode == Banner.Mode.OFF) {
return null;
}
ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader
: new DefaultResourceLoader(getClassLoader());
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);
}
printBanner
是banner打印的入口方法, 该方法根据不同的Banner.Mode
为枚举类型, 值有OFF
, CONSOLE
, LOG
, 分别对应不打印, 控制台输出, 输出到日志文件.
对于CONSOLE
和LOG
类型需要通过ResourceLoader resourceLoader
加载banner资源. banner打印需要通过SpringApplicationBannerPrinter
类中的print
方法(重载)进行处理, 对于CONSOLE
类型, 第三个参数为PrintStream
类型, 传入System.out
进行处理, LOG
类型则通过日志对象logger
(其定义为private static final Log logger = LogFactory.getLog(SpringApplication.class);)进行处理. 不过两个重载方法逻辑是一致的.
以CONSOLE
类型为例, 其代码为:
Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {
Banner banner = getBanner(environment);
banner.printBanner(environment, sourceClass, out);
return new PrintedBanner(banner, sourceClass);
}
首先要获取一个Banner对象(函数式), 调用打印方法printBanner
输出banner, 打印完毕, 构造一个PrintedBanner
继承于Banner
返回.
下面具体看一下getBanner
方法:
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;
}
Banners
类是Banner
接口的实现, 内部维护一个private final List<Banner> banners = new ArrayList<>();
用来存放所有的Banner, 该类也重写printBanner
为:
@Override
public void printBanner(Environment environment, Class<?> sourceClass, PrintStream out) {
for (Banner banner : this.banners) {
banner.printBanner(environment, sourceClass, out);
}
}
即: 将banners中的banner对象全部打印. 而banners中的banner来自于getImageBanner
和getTextBanner
加载的资源. 下面分别来看一下这两个方法.
getImageBanner
static final String BANNER_IMAGE_LOCATION_PROPERTY = "spring.banner.image.location";
private Banner getImageBanner(Environment environment) {
String location = environment.getProperty(BANNER_IMAGE_LOCATION_PROPERTY);
if (StringUtils.hasLength(location)) {
Resource resource = this.resourceLoader.getResource(location);
return resource.exists() ? new ImageBanner(resource) : null;
}
for (String ext : IMAGE_EXTENSION) {
Resource resource = this.resourceLoader.getResource("banner." + ext);
if (resource.exists()) {
return new ImageBanner(resource);
}
}
return null;
}
BANNER_IMAGE_LOCATION_PROPERTY
配置用来表示图片类型banner资源的加载位置, 之后resourceLoader
会生成一个对应位置资源的操作对象Resource resource
.
如果该位置下存在资源则返回ImageBanner
类型的对象, 该类也是Banner
的一个实现.
如果没有定义BANNER_IMAGE_LOCATION_PROPERTY
则resourceLoader
加载以banner
为前缀, "gif", "jpg", "png"
等格式的文件. 如果存在则直接返回一个ImageBanner
类型的对象.
注意如果不存在由于addIfNotNull
会判断Banner
是否为null, 是则不会将其放入banners
中.
getTextBanner
static final String BANNER_LOCATION_PROPERTY = "spring.banner.location";
static final String DEFAULT_BANNER_LOCATION = "banner.txt";
private Banner getTextBanner(Environment environment) {
String location = environment.getProperty(BANNER_LOCATION_PROPERTY, DEFAULT_BANNER_LOCATION);
Resource resource = this.resourceLoader.getResource(location);
if (resource.exists()) {
return new ResourceBanner(resource);
}
return null;
}
此时会从BANNER_LOCATION_PROPERTY
出加载banner.txt
文件, 并返回ResourceBanner
对象.
对于一个默认的系统配置, 无法加载图片和文本banner此时直接返回一个默认的DEFAULT_BANNER
, 其为SpringBootBanner
类型. 该类定义如下, 可以看到BANNER
为系统的默认
输出.
class SpringBootBanner implements Banner {
private static final String[] BANNER = { "", " . ____ _ __ _ _",
" /\\\\ / ___'_ __ _ _(_)_ __ __ _ \\ \\ \\ \\", "( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\",
" \\\\/ ___)| |_)| | | | | || (_| | ) ) ) )", " ' |____| .__|_| |_|_| |_\\__, | / / / /",
" =========|_|==============|___/=/_/_/_/" };
private static final String SPRING_BOOT = " :: Spring Boot :: ";
private static final int STRAP_LINE_SIZE = 42;
@Override
public void printBanner(Environment environment, Class<?> sourceClass, PrintStream printStream) {
for (String line : BANNER) {
printStream.println(line);
}
String version = SpringBootVersion.getVersion();
version = (version != null) ? " (v" + version + ")" : "";
StringBuilder padding = new StringBuilder();
while (padding.length() < STRAP_LINE_SIZE - (version.length() + SPRING_BOOT.length())) {
padding.append(" ");
}
printStream.println(AnsiOutput.toString(AnsiColor.GREEN, SPRING_BOOT, AnsiColor.DEFAULT, padding.toString(),
AnsiStyle.FAINT, version));
printStream.println();
}
}
自定义banner
从上面的分析我们可知, banner有三种形式:
- 图片
ImageBanner
, banner是在spring.banner.image.location
位置下的, 或者是默认路径下的banner.png|gif|jpg
文件 - 文本
ResourceBanner
, banner是在spring.banner.location
位置下的, 或者是默认路径下的banner.txt
文件. - 系统默认
SpringBootBanner
, banner样式是写在一个String数组中的
对于图片, 在资源目录下添加一个banner.png
文件, 运行:
另外springboot还提供了图片打印的一些参数控制, 比如长度,宽度,bitdepth
(值为4, 8, 分别只16色和256色), pixelmode
(值为block, 默认text, 分别对应方块输出和字符画)
一通配置之后的打印banner.
那么, 这些参数又是在什么时候生效的呢? 这就要看ImageBanner中重写的printBanner
下面的方法是核心方法, 就不分析了.
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());
}
}
对于文本, 在资源目录下添加banner.txt
文件. 运行: