首页 > 编程语言 >SpringBoot源码解析(六):打印Banner

SpringBoot源码解析(六):打印Banner

时间:2024-12-03 10:29:26浏览次数:12  
标签:SpringBoot Banner 打印 environment 源码 new 横幅 banner

SpringBoot源码系列文章

SpringBoot源码解析(一):SpringApplication构造方法

SpringBoot源码解析(二):引导上下文DefaultBootstrapContext

SpringBoot源码解析(三):启动开始阶段

SpringBoot源码解析(四):解析应用参数args

SpringBoot源码解析(五):准备应用环境

SpringBoot源码解析(六):打印Banner


目录

前言

  在前文中,我们深入解析了SpringBoot启动时应用环境的准备过程。接下来将深入介绍启动Banner打印的具体实现及流程。

SpringBoot版本2.7.18SpringApplication的run方法的执行逻辑如下,本文将详细介绍第5小节:打印启动Banner

// SpringApplication类方法
public ConfigurableApplicationContext run(String... args) {
    // 记录应用启动的开始时间
    long startTime = System.nanoTime();

    // 1.创建引导上下文,用于管理应用启动时的依赖和资源
    DefaultBootstrapContext bootstrapContext = createBootstrapContext();
    ConfigurableApplicationContext context = null;

    // 配置无头模式属性,以支持在无图形环境下运行
    // 将系统属性 java.awt.headless 设置为 true
    configureHeadlessProperty();

    // 2.获取Spring应用启动监听器,用于在应用启动的各个阶段执行自定义逻辑
    SpringApplicationRunListeners listeners = getRunListeners(args);
    // 启动开始方法(发布开始事件、通知应用监听器ApplicationListener)
    listeners.starting(bootstrapContext, this.mainApplicationClass);

    try {
        // 3.解析应用参数
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

        // 4.准备应用环境,包括读取配置文件和设置环境变量
        ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);

        // 配置是否忽略 BeanInfo,以加快启动速度
        configureIgnoreBeanInfo(environment);

        // 5.打印启动Banner
        Banner printedBanner = printBanner(environment);

        // 6.创建应用程序上下文
        context = createApplicationContext();
        
        // 设置应用启动的上下文,用于监控和管理启动过程
        context.setApplicationStartup(this.applicationStartup);

        // 7.准备应用上下文,包括加载配置、添加 Bean 等
        prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);

        // 8.刷新上下文,完成 Bean 的加载和依赖注入
        refreshContext(context);

        // 9.刷新后的一些操作,如事件发布等
        afterRefresh(context, applicationArguments);

        // 计算启动应用程序的时间,并记录日志
        Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
        }

        // 10.通知监听器应用启动完成
        listeners.started(context, timeTakenToStartup);

        // 11.调用应用程序中的 `CommandLineRunner` 或 `ApplicationRunner`,以便执行自定义的启动逻辑
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        // 12.处理启动过程中发生的异常,并通知监听器
        handleRunFailure(context, ex, listeners);
        throw new IllegalStateException(ex);
    }
    try {
        // 13.计算应用启动完成至准备就绪的时间,并通知监听器
        Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
        listeners.ready(context, timeTakenToReady);
    }
    catch (Throwable ex) {
        // 处理准备就绪过程中发生的异常
        handleRunFailure(context, ex, null);
        throw new IllegalStateException(ex);
    }

    // 返回已启动并准备就绪的应用上下文
    return context;
}

一、入口

// 5.打印启动Banner
Banner printedBanner = printBanner(environment);

// 打印启动 Banner 的方法,根据配置的 Banner 模式选择打印方式
private Banner printBanner(ConfigurableEnvironment environment) {
    // 如果 Banner 模式被设置为 OFF,则不打印 Banner,直接返回 null
    if (this.bannerMode == Banner.Mode.OFF) {
        return null;
    }

    // 确定资源加载器。如果当前实例的 resourceLoader 不为空,则使用它;否则创建一个默认的资源加载器
    ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader
            : new DefaultResourceLoader(null);

    // 创建 Banner 打印器,负责加载和打印 Banner
    SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);

    // 根据 Banner 模式决定打印到日志还是控制台
    if (this.bannerMode == Mode.LOG) {
        // 如果 Banner 模式为 LOG,则将 Banner 打印到日志中
        return bannerPrinter.print(environment, this.mainApplicationClass, logger);
    }

    // 默认情况下(CONSOLE 模式),将 Banner 打印到标准输出(控制台)
    return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}

二、Banner接口类

// 一个用于以编程方式输出 Banner 的接口类
@FunctionalInterface
public interface Banner {
	// 将 Banner 输出到指定的打印流
	void printBanner(Environment environment, Class<?> sourceClass, PrintStream out);

	// 用于配置 Banner 的模式枚举
	enum Mode {
		// 禁用 Banner 的打印
		OFF,
		// 将 Banner 输出到 System.out
		CONSOLE,
		// 将 Banner 输出到日志文件
		LOG
	}
}

1、打印Banner开关

  • 默认情况是打印到控制台

在这里插入图片描述

  • 可以通过properties或yml设置关闭打印Banner
spring.main.banner-mode=off

上一节有讲spring.main开头的属性会绑定到SpringApplication对象上,这样就可以通过配置文件的属性来决定Banner的打印模式。

三、打印Banner过程

1、console和log模式

  • console控制台模式,默认设置
// SpringApplicationBannerPrinter类方法
Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {
    // 根据提供的环境信息获取横幅。
    Banner banner = getBanner(environment);
    
    // 将横幅打印到指定的输出流中。
    banner.printBanner(environment, sourceClass, out);
    
    // 返回一个 PrintedBanner 对象,包含打印的横幅和源类信息。
    return new PrintedBanner(banner, sourceClass);
}
  • log日志文件模式,通过在配置文件中设置spring.main.banner-mode=log,可以将应用启动Banner输出到日志文件中
// SpringApplicationBannerPrinter类方法
Banner print(Environment environment, Class<?> sourceClass, Log logger) {
	// 根据提供的环境信息获取横幅。
	Banner banner = getBanner(environment);
	try {
		logger.info(createStringFromBanner(banner, environment, sourceClass));
	}
	catch (UnsupportedEncodingException ex) {
		logger.warn("Failed to create String for banner", ex);
	}
	// 返回一个 PrintedBanner 对象,包含打印的横幅和源类信息。
	return new PrintedBanner(banner, sourceClass);
}
  • log模式就是获取打印流内容转换为字符串,然后由log日志打印罢了

在这里插入图片描述

  两种方式都会返回一个PrintedBanner对象,主要是为以后在应用上下文中注册为Bean或供其他组件使用做准备。

2、四种Banner对象

  1. 图片和文本横幅组合的Banners
  2. 备用Banner
  3. 默认Banner
// SpringApplicationBannerPrinter类方法

// 默认Banner
private static final Banner DEFAULT_BANNER = new SpringBootBanner();

// 根据当前环境获取适当的横幅(Banner)
private Banner getBanner(Environment environment) {
    // 创建一个 Banners 对象,用于存储图片和文本横幅
    Banners banners = new Banners();
    // 尝试获取图片横幅,并将其添加到 Banners 中(如果非空)
    banners.addIfNotNull(getImageBanner(environment));
    // 尝试获取文本横幅,并将其添加到 Banners 中(如果非空)
    banners.addIfNotNull(getTextBanner(environment));
    // 如果至少包含一个横幅,则返回组合的 Banners 对象
    if (banners.hasAtLeastOneBanner()) {
        return banners;
    }
    
    // 如果没有任何横幅但存在备用横幅,则返回备用横幅
    if (this.fallbackBanner != null) {
        return this.fallbackBanner;
    }
    
    // 如果没有任何横幅,则返回默认横幅
    // Banner DEFAULT_BANNER = new SpringBootBanner();
    return DEFAULT_BANNER;
}
  • Banners对象内部持有多个Banner实现类,遍历调用Banner的printBanner方法

在这里插入图片描述

2.1、图片Banner

  • 尝试根据环境信息获取图片横幅(Image Banner)
  • 环境变量spring.banner.image.location用于指定图片路径;如果未设置,则默认加载路径为banner.gifbanner.jpgbanner.png(按顺序查找)。
private Banner getImageBanner(Environment environment) {
    // 从环境变量中获取横幅图片的路径
    // String BANNER_IMAGE_LOCATION_PROPERTY = "spring.banner.image.location";
    String location = environment.getProperty(BANNER_IMAGE_LOCATION_PROPERTY);
    
    // 如果路径不为空,尝试加载对应的资源
    if (StringUtils.hasLength(location)) {
        Resource resource = this.resourceLoader.getResource(location);
        // 如果资源存在,返回对应的 ImageBanner 对象
        return resource.exists() ? new ImageBanner(resource) : null;
    }

    // 如果未指定路径,尝试加载默认图片横幅文件
    // String[] IMAGE_EXTENSION = { "gif", "jpg", "png" };
    for (String ext : IMAGE_EXTENSION) {
        Resource resource = this.resourceLoader.getResource("banner." + ext);
        // 如果找到存在的文件资源,返回对应的 ImageBanner 对象
        if (resource.exists()) {
            return new ImageBanner(resource);
        }
    }
    // 如果没有找到任何图片横幅资源,返回 null
    return null;
}

控制台效果

在这里插入图片描述

2.2、文字Banner

  • 尝试根据环境信息获取文本横幅(Text Banner)
  • 环境变量spring.banner.location用于指定文本路径;如果未设置,则默认加载路径为banner.txt
private Banner getTextBanner(Environment environment) {
    // 获取横幅的路径,优先使用环境变量中的配置,如果没有配置则使用默认路径
    // String BANNER_LOCATION_PROPERTY = "spring.banner.location";
    // String DEFAULT_BANNER_LOCATION = "banner.txt";
    String location = environment.getProperty(BANNER_LOCATION_PROPERTY, DEFAULT_BANNER_LOCATION);

    // 使用 ResourceLoader 加载指定路径的资源
    Resource resource = this.resourceLoader.getResource(location);

    try {
        // 检查资源是否存在,且路径中不包含 "liquibase-core"(防止加载到不相关的资源)
        if (resource.exists() && !resource.getURL().toExternalForm().contains("liquibase-core")) {
            // 如果资源有效,返回对应的 ResourceBanner 对象
            return new ResourceBanner(resource);
        }
    } catch (IOException ex) {
        // 忽略异常,可能是资源加载时出错或路径无效
        // 在这里不抛出异常,而是返回 null,表示没有有效的横幅资源
    }
    // 如果资源无效或发生异常,返回 null
    return null;
}

控制台效果

在这里插入图片描述

2.2、备用Banner

  SpringApplicationBannerPrinter对象的备用Banner属性fallbackBanner是由SpringApplication对象的私有属性banner传递而来的。

在这里插入图片描述

  • 可以在SpringBoot启动类中通过调用SpringApplicationsetBanner方法直接设置自定义的Banner对象
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(Application.class);
        // 设置全局备用横幅
        app.setBanner((environment, sourceClass, out) -> {
            out.println("===== 全局备用横幅 =====");
            out.println(" 默认横幅已启用 ");
            out.println("=====================");
        });
        app.run(args);
    }
}

控制台效果

在这里插入图片描述

2.3、默认Banner

  如果未设置自定义文字图片Banner或备用Banner,SpringBoot将使用默认的启动横幅(SpringBootBanner)作为显示内容。

// 默认的 Banner 实现,用于打印 "Spring" 的启动横幅,和版本信息
class SpringBootBanner implements Banner {

    // 预定义的 ASCII 艺术横幅,每行为一个数组元素
	private static final String[] BANNER = { 
		"", 
		"  .   ____          _            __ _ _",
		" /\\\\ / ___'_ __ _ _(_)_ __  __ _ \\ \\ \\ \\",
		"( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\",
		" \\\\/  ___)| |_)| | | | | || (_| |  ) ) ) )", 
		"  '  |____| .__|_| |_|_| |_\\__, | / / / /",
		" =========|_|==============|___/=/_/_/_/" 
	};

	// 固定的 Spring Boot 标识符,用于横幅输出
	private static final String SPRING_BOOT = " :: Spring Boot :: ";

	// 横幅固定宽度,用于计算 padding 的空格数
	private static final int STRAP_LINE_SIZE = 42;

	/**
	 * 输出横幅到指定的 PrintStream(如控制台或日志)。
	 */
	@Override
	public void printBanner(Environment environment, Class<?> sourceClass, PrintStream printStream) {
		// 遍历并打印每一行 ASCII 艺术横幅
		for (String line : BANNER) {
			printStream.println(line);
		}
		// 获取 Spring Boot 的版本信息
		String version = SpringBootVersion.getVersion();
		// 如果版本信息不为空,则格式化为 "(vX.X.X)"
		version = (version != null) ? " (v" + version + ")" : "";
		
		// 构造 padding 空格,使横幅版本号对齐
		StringBuilder padding = new StringBuilder();
		while (padding.length() < STRAP_LINE_SIZE - (version.length() + SPRING_BOOT.length())) {
			padding.append(" ");
		}

		// 打印 Spring Boot 标识符和版本信息,使用 ANSI 输出样式
		printStream.println(AnsiOutput.toString(
			AnsiColor.GREEN,       // 绿色输出 Spring Boot 标识符
			SPRING_BOOT, 
			AnsiColor.DEFAULT,     // 恢复默认颜色
			padding.toString(),    // 填充的空格
			AnsiStyle.FAINT,       // 微弱样式(淡化显示版本信息)
			version                // 版本号
		));
		// 添加空行用于分隔横幅和其他输出
		printStream.println();
	}
}

控制台效果

在这里插入图片描述

总结

  本文全面解析了SpringBoot启动横幅的实现原理、打印流程及自定义方法,介绍了文本横幅(默认路径为banner.txt,可通过spring.banner.location配置)、图片横幅(默认路径为banner.gifbanner.jpgbanner.png,可通过spring.banner.image.location配置)、备用横幅默认横幅的使用,帮助开发者灵活运用横幅机制提升项目启动体验。

标签:SpringBoot,Banner,打印,environment,源码,new,横幅,banner
From: https://blog.csdn.net/qq_35512802/article/details/144129076

相关文章

  • SpringBoot 驱动下基于 MVC 的高校行政事务管理系统:设计蓝图与实现路径
    1绪论1.1课题背景身处网络时代,随着网络系统体系发展的不断成熟和完善,人们的生活也随之发生了很大的变化。目前,人们在追求较高物质生活的同时,也在想着如何使自身的精神内涵得到提升,而读书就是人们获得精神享受非常重要的途径[1]。纸质版的书籍比较沉重,携带不方便,而由于手机......
  • 基于 MVC 架构的 SpringBoot 高校办公室行政事务管理系统:创新设计与高效实现
    1绪论1.1课题背景身处网络时代,随着网络系统体系发展的不断成熟和完善,人们的生活也随之发生了很大的变化。目前,人们在追求较高物质生活的同时,也在想着如何使自身的精神内涵得到提升,而读书就是人们获得精神享受非常重要的途径[1]。纸质版的书籍比较沉重,携带不方便,而由于手机......
  • 毕业设计-ssm汽车租赁系统(案例分析)-附源码
    摘 要众所周知,汽车租赁系统被称为交通运输服务行,它因为无须办理保险、无须年检维修、车型可随意更换等优点,以租车代替买车来控制企业成本,这种在外企中十分流行的管理方式,正慢慢受到国内企事业单位和个人用户的青睐。汽车租赁是指将汽车的资产使用权从拥有权中分开,出租人......
  • springboot在线宠物用品交易网站的设计与实现(代码+数据库+LW)
    摘要随着信息技术在管理上越来越深入而广泛的应用,管理信息系统的实施在技术上已逐步成熟。本文介绍了在线宠物用品交易网站的开发全过程。通过分析在线宠物用品交易网站管理的不足,创建了一个计算机管理在线宠物用品交易网站的方案。文章介绍了在线宠物用品交易网站的系统分析......
  • springboot房屋租赁管理系统的设计与实现(代码+数据库+LW)
    摘 要互联网发展至今,无论是其理论还是技术都已经成熟,而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播,搭配信息管理工具可以很好地为人们提供服务。针对房屋租赁信息管理混乱,出错率高,信息安全性差,劳动强度大,费时费力等问题,采用房屋租赁管理系统可以有效管理,使......
  • 【雷达】对萨德系统ANTPY-2雷达的威力范围仿真计算【含Matlab源码 9707期】
    ......
  • Y20030035 基于微信小程序+Java+SpringBoot+vue+maven+mysql+的车位租赁管理系统设计
    车位租赁管理系统1.项目描概述2.开发的背景与意义3.功能结构4.界面展示5.源码获取1.项目描概述在移动互联网的迅速发展推进下,微信成了人们生活中不可缺少的一款信息交流和沟通平台。而微信小程序的推出,便得现在人们在日常生活中更多的是通过手机微信平台进行安装各......
  • springboot毕设病历管理系统程序+论文
    系统程序文件列表开题报告内容研究背景在当今快速发展的医疗行业中,病历管理作为医院日常运营的重要组成部分,其效率与准确性直接关系到医疗服务的质量和患者的满意度。随着患者数量的不断增加,传统的病历管理方式逐渐暴露出诸多不足,如信息检索困难、存储空间有限、数据安全难......
  • y20030034 微信小程序+java+jsp+servlet+mysql+电子设备回收小程序 源码 配置 文档
    电子设备回收小程序1.摘要2.开发背景和意义3.功能结构4.界面展示5.源码获取1.摘要随着移动互联网的发展,微信小程序已经成为人们生活中不可或缺的一部分。微信小程序的优点在于其快速、轻量、易用,用户无需下载即可使用,节省了用户的时间和空间。随着人们对环保意识的......
  • springboot毕设病历管理系统程序+论文
    系统程序文件列表开题报告内容研究背景在医疗领域,病历管理是一项至关重要的工作,它不仅关系到患者的个人健康信息,也直接影响到医疗服务的质量和效率。随着医疗信息化的发展,传统的纸质病历管理方式逐渐暴露出存储不便、检索效率低下、易丢失损坏等问题。特别是在大型综合医院......