SpringBoot之Banner源码深度分解
在对源码解析之前,先给大家推荐一波spring全家桶的学习资料:
阿里P8架构师手写笔记:Spring源码+JVM+MySQL+Kafka+Tomcat
Java程序员还没有掌握SpringBoot?这一份文档,你真应该好好学学
手写Spring全家桶,阿里架构师的Java开发经验,展现在这文档里
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结合使用。
仔细思考“面向过程编程、面向对象编程、面向切面编程、面向服务编程”这些设计思想的演变,它们有一种规律在其中。可以说是技术生产力的能力在不断提高不断释放,它们是递进的逐步提出的概念并发展开来。
本文作者: Owen Jia
本文链接: https://blog.shareworld.vip/archives/banner