本文为原创,转载请标明出处!
我们都知道SpringBoot在启动时会打印一个Banner,就是一个SpringBoot的标志,如下:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.0.5.RELEASE)
那么这个标志是怎么打印出来的,如何修改呢?今天就从源码级别进行相关解析和修改处理。
1、源码分析
启动SpringBoot的时候,常用的方式是使用SpringApplication.run(xxx.class, args);
当然还有几种其他的启动方式(后面在说明修改Banner时会涉及),但无论哪种启动方式,最后都会运行SpringApplication的run方法,下面是这个run方法的源码:
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment); // 此处打印
......
上面加注释的那部分就是其打印Banner的代码,我们再打开这个printBanner()看下源码,如下:
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);
}
其中涉及到了一个BannerMode的概念,这个是一个枚举,描述的是打印Banner的模式:
enum Mode {
/**
* Disable printing of the banner.
*/
OFF,
/**
* Print the banner to System.out.
*/
CONSOLE,
/**
* Print the banner to the log file.
*/
LOG
}
源码已经注释的非常清晰,有三种模式,此处不再赘述。
书接上文,我们发现绕过一些判断的方法,最后都是执行的bannerPrinter.print方法,这个bannerPrinter是一个类:SpringApplicationBannerPrinter,我们打开这部分源码继续分析:
public 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;
}
处理逻辑其实很简单,就是首先获取当前环境的Banner(Banner其实是一个接口),然后执行这个banner的printBanner方法。SpringBoot在启动的时候使用的是DEFAULT_BANNER,DEFAULT_BANNER对应的类是:
private static final Banner DEFAULT_BANNER = new SpringBootBanner();
到这里我们已经很清楚了,其实SpringBoot在启动时调用的是SpringBootBanner这个类,下面分析一下这个类。
首先是SpringBootBanner的源码:
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();
}
}
哈哈,是不是很开心的看到了SpringBoot启动时打印的图标?
从上面我们可以很清晰的看出:
1)这个类实现了Banner这个接口;
2)它打印了Banner信息;
在这里我们其实也学习到了另外的一个知识:那就是获取版本号的处理。
String version = SpringBootVersion.getVersion();
我们看下SpringBootVersion的源码,我们发现其实它还是调用的JDK提供的package来获取的:
public final class SpringBootVersion {
private SpringBootVersion() {
}
/**
* Return the full version string of the present Spring Boot codebase, or {@code null}
* if it cannot be determined.
* @return the version of Spring Boot or {@code null}
* @see Package#getImplementationVersion()
*/
public static String getVersion() {
Package pkg = SpringBootVersion.class.getPackage();
return (pkg != null) ? pkg.getImplementationVersion() : null;
}
}
2、修改Banner
下面我们自己定义一个图标进行打印:
首先定义一个类:MyBanner,该类需要实现Banner接口,我们参考SpringBootBanner的处理方式,源码如下:
public class MyBanner implements Banner {
private static final String[] BANNER = {
"-------------------------",
"| My Name is SpringBoot |",
"-------------------------"};
@Override
public void printBanner(Environment environment, Class<?> sourceClass, PrintStream printStream) {
for (String line : BANNER) {
printStream.println(line);
}
printStream.println();
}
}
下面我们修改SpringBoot的启动方式,前面我们已经提到了SpringBoot最基本的启动方式:
SpringApplication.run(BootStart.class, args);
还有两种常用的启动方式:
SpringApplication springApplication = new SpringApplication(BootStart.class);
springApplication.run(args);
和
new SpringApplicationBuilder().sources(BootStart.class).run(args);
而我们修改Banner必须依赖于下面的这两种方式。
1)第一种修改,源码如下:
SpringApplication springApplication = new SpringApplication(BootStart.class);
springApplication.setBanner(new MyBanner()); // 设置为自定义的Banner
springApplication.run(args);
2)第二种修改,源码如下:
new SpringApplicationBuilder().banner(new MyBanner()).sources(BootStart.class).run(args);
通过上述两种修改都可以达到我们想要的效果:
为什么可以达到这个效果呢?答案还是在原来我们看到的SpringApplicationBannerPrinter获取Banner的方法中,源码:
public 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;
}
这里面有一个fallbackBanner,而这个fallbackBanner恰好就是我们通过setBanner方法设置的,由下面几段代码可以清晰的看出,其实这些代码原先都看过:
public void setBanner(Banner banner) {
this.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); // 就是这个banner
if (this.bannerMode == Mode.LOG) {
return bannerPrinter.print(environment, this.mainApplicationClass, logger);
}
return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}
SpringApplicationBannerPrinter(ResourceLoader resourceLoader, Banner fallbackBanner) {
this.resourceLoader = resourceLoader;
this.fallbackBanner = fallbackBanner; // 就是setBanner中的那个Banner
}
这样就一目了然了~~~~~~