java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot打印Banner

SpringBoot打印Banner的实现示例

作者:冬天vs不冷

本文主要介绍了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开关

在这里插入图片描述

spring.main.banner-mode=off

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

三、打印Banner过程

1、console和log模式

// SpringApplicationBannerPrinter类方法
Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {
    // 根据提供的环境信息获取横幅。
    Banner banner = getBanner(environment);
    
    // 将横幅打印到指定的输出流中。
    banner.printBanner(environment, sourceClass, out);
    
    // 返回一个 PrintedBanner 对象,包含打印的横幅和源类信息。
    return new PrintedBanner(banner, sourceClass);
}
// 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);
}

在这里插入图片描述

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

2、四种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;
}

在这里插入图片描述

2.1、图片Banner

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

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传递而来的。

在这里插入图片描述

@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的实现示例的文章就介绍到这了,更多相关SpringBoot打印Banner内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文