1. 程式人生 > >SpringBoot啟動Banner設定

SpringBoot啟動Banner設定

本文為原創,轉載請標明出處!

我們都知道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
	}

這樣就一目瞭然了~~~~~~