From 874ee9936a75cee93294e3ead5a954f570b71bb9 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 13 Feb 2025 10:13:12 +0000 Subject: [PATCH] Improve handling of default values when printing the banner Previously, default values for the following properties did not work: - application.title - application.formatted-version - application.version - spring-boot.formatted-version - spring-boot.version Instead of the default value, an empty string was used instead. For example, ${application.title:Title} would be replaced with "" rather than "Title" when the application title was unavailable. This commit improves the ResourceBanner so that a placeholder's default value is used. An empty string will still be used when no default value is provided. For example, ${application.title} will be replaced with "". As before, custom properties that are not well-known will not be replaced at all. For example ${custom.property} will remain as-is in the printed banner when the custom.property has not been set. Fixes gh-44137 --- .../springframework/boot/ResourceBanner.java | 52 ++++++++++++------- .../boot/ResourceBannerTests.java | 30 +++++++++-- 2 files changed, 59 insertions(+), 23 deletions(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ResourceBanner.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ResourceBanner.java index 98b2e6dd95c..4164f92bd2a 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ResourceBanner.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ResourceBanner.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -85,22 +85,34 @@ public class ResourceBanner implements Banner { * @return a mutable list of property resolvers */ protected List getPropertyResolvers(Environment environment, Class sourceClass) { - MutablePropertySources sources = new MutablePropertySources(); - if (environment instanceof ConfigurableEnvironment configurableEnvironment) { - configurableEnvironment.getPropertySources().forEach(sources::addLast); - } - sources.addLast(getTitleSource(sourceClass)); - sources.addLast(getAnsiSource()); - sources.addLast(getVersionSource(sourceClass)); List resolvers = new ArrayList<>(); - resolvers.add(new PropertySourcesPropertyResolver(sources)); + resolvers.add(new PropertySourcesPropertyResolver(createNullDefaultSources(environment, sourceClass))); + resolvers.add(new PropertySourcesPropertyResolver(createEmptyDefaultSources(sourceClass))); return resolvers; } - private MapPropertySource getTitleSource(Class sourceClass) { + private MutablePropertySources createNullDefaultSources(Environment environment, Class sourceClass) { + MutablePropertySources nullDefaultSources = new MutablePropertySources(); + if (environment instanceof ConfigurableEnvironment configurableEnvironment) { + configurableEnvironment.getPropertySources().forEach(nullDefaultSources::addLast); + } + nullDefaultSources.addLast(getTitleSource(sourceClass, null)); + nullDefaultSources.addLast(getAnsiSource()); + nullDefaultSources.addLast(getVersionSource(sourceClass, null)); + return nullDefaultSources; + } + + private MutablePropertySources createEmptyDefaultSources(Class sourceClass) { + MutablePropertySources emptyDefaultSources = new MutablePropertySources(); + emptyDefaultSources.addLast(getTitleSource(sourceClass, "")); + emptyDefaultSources.addLast(getVersionSource(sourceClass, "")); + return emptyDefaultSources; + } + + private MapPropertySource getTitleSource(Class sourceClass, String defaultValue) { String applicationTitle = getApplicationTitle(sourceClass); Map titleMap = Collections.singletonMap("application.title", - (applicationTitle != null) ? applicationTitle : ""); + (applicationTitle != null) ? applicationTitle : defaultValue); return new MapPropertySource("title", titleMap); } @@ -119,18 +131,18 @@ public class ResourceBanner implements Banner { return new AnsiPropertySource("ansi", true); } - private MapPropertySource getVersionSource(Class sourceClass) { - return new MapPropertySource("version", getVersionsMap(sourceClass)); + private MapPropertySource getVersionSource(Class sourceClass, String defaultValue) { + return new MapPropertySource("version", getVersionsMap(sourceClass, defaultValue)); } - private Map getVersionsMap(Class sourceClass) { + private Map getVersionsMap(Class sourceClass, String defaultValue) { String appVersion = getApplicationVersion(sourceClass); String bootVersion = getBootVersion(); Map versions = new HashMap<>(); - versions.put("application.version", getVersionString(appVersion, false)); - versions.put("spring-boot.version", getVersionString(bootVersion, false)); - versions.put("application.formatted-version", getVersionString(appVersion, true)); - versions.put("spring-boot.formatted-version", getVersionString(bootVersion, true)); + versions.put("application.version", getVersionString(appVersion, false, defaultValue)); + versions.put("spring-boot.version", getVersionString(bootVersion, false, defaultValue)); + versions.put("application.formatted-version", getVersionString(appVersion, true, defaultValue)); + versions.put("spring-boot.formatted-version", getVersionString(bootVersion, true, defaultValue)); return versions; } @@ -143,9 +155,9 @@ public class ResourceBanner implements Banner { return SpringBootVersion.getVersion(); } - private String getVersionString(String version, boolean format) { + private String getVersionString(String version, boolean format, String fallback) { if (version == null) { - return ""; + return fallback; } return format ? " (v" + version + ")" : version; } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ResourceBannerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ResourceBannerTests.java index 8ba272032d4..a1c2b39d0ff 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ResourceBannerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ResourceBannerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -69,6 +69,14 @@ class ResourceBannerTests { assertThat(banner).startsWith("banner 1 "); } + @Test + void renderWithoutVersionsWithDefaultValues() { + Resource resource = new ByteArrayResource( + "banner ${a} ${spring-boot.version:X.Y.Z} ${application.version:A.B.C}".getBytes()); + String banner = printBanner(resource, null, null, null); + assertThat(banner).startsWith("banner 1 X.Y.Z A.B.C"); + } + @Test void renderFormattedVersions() { Resource resource = new ByteArrayResource( @@ -80,9 +88,18 @@ class ResourceBannerTests { @Test void renderWithoutFormattedVersions() { Resource resource = new ByteArrayResource( - "banner ${a}${spring-boot.formatted-version}${application.formatted-version}".getBytes()); + "banner ${a} ${spring-boot.formatted-version} ${application.formatted-version}".getBytes()); String banner = printBanner(resource, null, null, null); - assertThat(banner).startsWith("banner 1"); + assertThat(banner).startsWith("banner 1 "); + } + + @Test + void renderWithoutFormattedVersionsWithDefaultValues() { + Resource resource = new ByteArrayResource( + "banner ${a} ${spring-boot.formatted-version:(vX.Y.Z)} ${application.formatted-version:(vA.B.C)}" + .getBytes()); + String banner = printBanner(resource, null, null, null); + assertThat(banner).startsWith("banner 1 (vX.Y.Z) (vA.B.C)"); } @Test @@ -131,6 +148,13 @@ class ResourceBannerTests { assertThat(banner).startsWith("banner 1"); } + @Test + void renderWithoutTitleWithDefaultValue() { + Resource resource = new ByteArrayResource("banner ${application.title:Default Title} ${a}".getBytes()); + String banner = printBanner(resource, null, null, null); + assertThat(banner).startsWith("banner Default Title 1"); + } + @Test void renderWithDefaultValues() { Resource resource = new ByteArrayResource(