本篇侧重点是源码层面的分析,SpringBoot基础知识需要先有所了解,才能更好跟上节奏。
Banner更多的作为一种人性化的标志,比如企业的Flag、某个知名产品的Flag、不同环境的Flag、等等。SpringBoot大道至简的思想就是要将Banner非功能需求和部分功能需求都封装好,给用户提供最傻瓜的操作步骤去使用它。
采用SpringBoot默认配置方式
不需要改动任何东西,只需要在resources目录下添加banner.txt文件即可。
banner.txt内容格式:
. ____ _ __ _ _ /// / ___'_ __ _ _(_)_ __ __ _ / / / / ( ( )/___ | '_ | '_| | '_ // _` | / / / / /// ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_/__, | / / / / =========|_|==============|___/=/_/_/_/ :: Hello World :: ${application.formatted-version}
Banner.txt支持变量参数:
Banner相关的类都在spring-boot包中。
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是支持自定义扩展类的,只有通过这个方法在启动时设置就可以了。
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 {...}
/** * 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 } }
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(); } }
原理同SpringBootBanner类似,多了image模块逻辑,源码不贴了
图片格式Banner,SpringBoot加载配置项banner.image.location,从配置项中获取真实的路径,SpringBoot 会根据配置项的路径加载文件。如果没有配置banner.image.location,转而依次加载resource目录下的banner.gif、banner.jpg、 banner.png这三个中存在的文件,转化成txt进行输出。
原理同SpringBootBanner类似,多了resource模块逻辑,源码不贴了
Banner.txt支持PlaceHolder变量使用“${}”,支持的参与有application.version、spring-boot.version、application.formatted-version、spring-boot.formatted-version、application.title。
有个特别的参数是AnsiColor类,如“AnsiColor.GREEN”,可以控制输出字符颜色,在banner.txt里面可以加多个进行分段控制。
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); } }
起到封装多个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); } } }
Banner是比较简单的设计架构,把OOP思想发挥的非常好值得我辈学习,其中值得一提的“PrintedBanner”类设计就值得考究,OOP发挥到极致!
OOP思想存在已经很多年了,架构设计层面一般是OOP+AOP结合使用。
自动生成ASCII字符图案网站:
http://www.network-science.de/ascii/
http://patorjk.com/software/taag
https://www.degraeve.com/img2txt.php