SpringBoot Banner 架构原理
本篇侧重点是源码层面的分析,SpringBoot基础知识需要先有所了解,才能更好跟上节奏。
Banner更多的作为一种人性化的标志,比如企业的Flag、某个知名产品的Flag、不同环境的Flag、等等。SpringBoot大道至简的思想就是要将Banner非功能需求和部分功能需求都封装好,给用户提供最傻瓜的操作步骤去使用它。
每一个应用都应该有自己的Banner,独一无二。
常规使用方式
采用SpringBoot默认配置方式
不需要改动任何东西,只需要在resources目录下添加banner.txt文件即可。
banner.txt内容格式:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Hello World :: ${application.formatted-version}
Banner.txt支持变量参数:
${AnsiColor.BRIGHT_RED}
:设置控制台中输出内容的颜色。${application.version}
:用来获取MANIFEST.MF文件中的版本号。${application.formatted-version}
:格式化后的${application.version}
版本信息。${spring-boot.version}
:Spring Boot的版本号。${spring-boot.formatted-version}
:格式化后的${spring-boot.version}
版本信息。
类组织关系解析:spring-boot.jar
Banner相关的类都在spring-boot包中。
- Banner:一个抽象接口,值得注意的是里面的enum Mode用来控制Banner打印的方式:LOG\CONSOLE\OFF。
- SpringBootBanner:实现Banner接口,是SpringBoot默认类,用来打印“SpringBoot”图案。
- ImageBanner:实现Banner接口,是一个用来把banner.git|jpg|png类图片转换为ASCII字符图案的类。
- ResouceBanner: 实现Banner接口,一般的banner.txt的资源文件类。
- Banners:实现Banner接口,S..A..BannerPrinter的私有静态类,封装多个Banner的集合类。
- PrintedBanner:实现Banner接口,S..A..BannerPrinter的私有静态类,封装资源和对应Banner。
- SpringApplicationBannerPrinter:执行逻辑控制单元,核心类,负责同SpringApplication对接。
- SpringApplication:它不属于Banner架构部分,但是它是banner启动执行的源头。
class SpringApplication
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);
}
/**
* Sets the {@link Banner} instance which will be used to print the banner when no
* static banner file is provided.
* @param banner the Banner instance to use
*/
public void setBanner(Banner banner) {
this.banner = banner;
}
/**
* Sets the mode used to display the banner when the application runs. Defaults to
* {@code Banner.Mode.CONSOLE}.
* @param bannerMode the mode used to display the banner
*/
public void setBannerMode(Banner.Mode bannerMode) {
this.bannerMode = bannerMode;
}
重点:
setBanner(Banner)方法告诉我们Banner是支持自定义扩展类的,只有通过这个方法在启动时设置就可以了。
class SpringApplicationBannerPrinter
static final String BANNER_LOCATION_PROPERTY = "banner.location";
static final String BANNER_IMAGE_LOCATION_PROPERTY = "banner.image.location";
static final String DEFAULT_BANNER_LOCATION = "banner.txt";
static final String[] IMAGE_EXTENSION = { "gif", "jpg", "png" };
private static final Banner DEFAULT_BANNER = new SpringBootBanner();
private final ResourceLoader resourceLoader;
private final Banner fallbackBanner;
SpringApplicationBannerPrinter(ResourceLoader resourceLoader, Banner fallbackBanner) {
this.resourceLoader = resourceLoader;
this.fallbackBanner = fallbackBanner;
}
public Banner print(Environment environment, Class<?> sourceClass, Log logger) {
Banner banner = getBanner(environment, this.fallbackBanner);
try {
logger.info(createStringFromBanner(banner, environment, sourceClass));
}
catch (UnsupportedEncodingException ex) {
logger.warn("Failed to create String for banner", ex);
}
return new PrintedBanner(banner, sourceClass);
}
public Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {
Banner banner = getBanner(environment, this.fallbackBanner);
banner.printBanner(environment, sourceClass, out);
return new PrintedBanner(banner, sourceClass);
}
private Banner getBanner(Environment environment, Banner definedBanner) {
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;
}
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;
}
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;
}
private String createStringFromBanner(Banner banner, Environment environment,
Class<?> mainApplicationClass) throws UnsupportedEncodingException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
banner.printBanner(environment, mainApplicationClass, new PrintStream(baos));
String charset = environment.getProperty("banner.charset", "UTF-8");
return baos.toString(charset);
}
private static class Banners implements Banner {...}
private static class PrintedBanner implements Banner {...}
重点:
- ImageBanner和ResouceBanner是同级共存的,如何两者同时有就会都打印出来,Image先打印。
- Banners和PrintedBanner是它私有静态类,工具类概念使用。
interface Banner
/**
* Interface class for writing a banner programmatically.
*
* @author Phillip Webb
* @author Michael Stummvoll
* @author Jeremy Rickard
* @since 1.2.0
*/
@FunctionalInterface
public interface Banner {
/**
* Print the banner to the specified print stream.
* @param environment the spring environment
* @param sourceClass the source class for the application
* @param out the output print stream
*/
void printBanner(Environment environment, Class<?> sourceClass, PrintStream out);
/**
* An enumeration of possible values for configuring the Banner.
*/
enum Mode {
/**
* Disable printing of the banner.
*/
OFF,
/**
* Print the banner to System.out.
*/
CONSOLE,
/**
* Print the banner to the log file.
*/
LOG
}
}
SpringBootBanner
SpringBoot框架默认Banner,什么都不配置就打印它。
/**
* Default Banner implementation which writes the 'Spring' banner.
*
* @author Phillip Webb
*/
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 + ")");
String padding = "";
while (padding.length() < STRAP_LINE_SIZE
- (version.length() + SPRING_BOOT.length())) {
padding += " ";
}
printStream.println(AnsiOutput.toString(AnsiColor.GREEN, SPRING_BOOT,
AnsiColor.DEFAULT, padding, AnsiStyle.FAINT, version));
printStream.println();
}
}
ImageBanner
原理同SpringBootBanner类似,多了image模块逻辑,源码不贴了
图片格式Banner,SpringBoot加载配置项banner.image.location,从配置项中获取真实的路径,SpringBoot 会根据配置项的路径加载文件。如果没有配置banner.image.location,转而依次加载resource目录下的banner.gif、banner.jpg、 banner.png这三个中存在的文件,转化成txt进行输出。
ResourceBanner
原理同SpringBootBanner类似,多了resource模块逻辑,源码不贴了
Banner.txt支持PlaceHolder变量使用“${}”,支持的参与有application.version、spring-boot.version、application.formatted-version、spring-boot.formatted-version、application.title。
有个特别的参数是AnsiColor类,如“AnsiColor.GREEN”,可以控制输出字符颜色,在banner.txt里面可以加多个进行分段控制。
SpringApplicationBannerPrinter.PrintedBanner
Banner调用返回类,作为一个封装的数据结果返回给SpringApplication。
private static class PrintedBanner implements Banner {
private final Banner banner;
private final Class<?> sourceClass;
PrintedBanner(Banner banner, Class<?> sourceClass) {
this.banner = banner;
this.sourceClass = sourceClass;
}
@Override
public void printBanner(Environment environment, Class<?> sourceClass,
PrintStream out) {
sourceClass = (sourceClass == null ? this.sourceClass : sourceClass);
this.banner.printBanner(environment, sourceClass, out);
}
}
SpringApplicationBannerPrinter.Banners
起到封装多个Banner的作用,随着发展而出现的一个类。
private static class Banners implements Banner {
private final List<Banner> banners = new ArrayList<Banner>();
public void addIfNotNull(Banner banner) {
if (banner != null) {
this.banners.add(banner);
}
}
public boolean hasAtLeastOneBanner() {
return !this.banners.isEmpty();
}
@Override
public void printBanner(Environment environment, Class<?> sourceClass,
PrintStream out) {
for (Banner banner : this.banners) {
banner.printBanner(environment, sourceClass, out);
}
}
}
总结归纳
这块的源码建议多去仔细品味一点点的解读,没有什么难度的Code,很适合作为SpringBoot思想的入门学习。Banner总体来说是比较简单的架构设计,其中把OOP思想发挥的非常好很值得我辈学习,值得一提的“PrintedBanner”类设计就值得考究,OOP发挥到极致!
OOP思想存在已经很多年了,架构设计中一般是OOP+AOP结合使用。
仔细思考“面向过程编程、面向对象编程、面向切面编程、面向服务编程”这些设计思想的演变,它们有一种规律在其中。可以说是技术生产力的能力在不断提高不断释放,它们是递进的逐步提出的概念并发展开来。
友链推荐
自动生成ASCII字符图案网站:
http://www.network-science.de/ascii/