Skip to content

Commit 46ea2b3

Browse files
committed
Allow changing the banner printer
1 parent af34690 commit 46ea2b3

File tree

6 files changed

+279
-128
lines changed

6 files changed

+279
-128
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot;
18+
19+
import java.io.ByteArrayOutputStream;
20+
import java.io.IOException;
21+
import java.io.PrintStream;
22+
import java.io.UnsupportedEncodingException;
23+
import java.nio.charset.StandardCharsets;
24+
25+
import org.apache.commons.logging.Log;
26+
import org.apache.commons.logging.LogFactory;
27+
28+
import org.springframework.boot.Banner.Mode;
29+
import org.springframework.core.env.Environment;
30+
import org.springframework.core.io.Resource;
31+
import org.springframework.core.io.ResourceLoader;
32+
33+
/**
34+
* Class used by {@link SpringApplication} to print the application banner.
35+
*
36+
* @author Phillip Webb
37+
*/
38+
class DefaultSpringApplicationBannerPrinter implements SpringApplicationBannerPrinter {
39+
40+
private static final Log logger = LogFactory.getLog(DefaultSpringApplicationBannerPrinter.class);
41+
42+
private static final Banner DEFAULT_BANNER = new SpringBootBanner();
43+
44+
private final ResourceLoader resourceLoader;
45+
46+
private final Banner fallbackBanner;
47+
48+
DefaultSpringApplicationBannerPrinter(ResourceLoader resourceLoader, Banner fallbackBanner) {
49+
this.resourceLoader = resourceLoader;
50+
this.fallbackBanner = fallbackBanner;
51+
}
52+
53+
@Override
54+
public Banner print(Environment environment, Class<?> sourceClass, Mode bannerMode) {
55+
switch (bannerMode) {
56+
case CONSOLE -> {
57+
return print(environment, sourceClass, System.out);
58+
}
59+
case LOG -> {
60+
return print(environment, sourceClass, logger);
61+
}
62+
}
63+
return new PrintedBanner(getBanner(environment), sourceClass);
64+
}
65+
66+
private Banner print(Environment environment, Class<?> sourceClass, Log logger) {
67+
Banner banner = getBanner(environment);
68+
try {
69+
logger.info(createStringFromBanner(banner, environment, sourceClass));
70+
}
71+
catch (UnsupportedEncodingException ex) {
72+
logger.warn("Failed to create String for banner", ex);
73+
}
74+
return new PrintedBanner(banner, sourceClass);
75+
}
76+
77+
private Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {
78+
Banner banner = getBanner(environment);
79+
banner.printBanner(environment, sourceClass, out);
80+
return new PrintedBanner(banner, sourceClass);
81+
}
82+
83+
private Banner getBanner(Environment environment) {
84+
Banner textBanner = getTextBanner(environment);
85+
if (textBanner != null) {
86+
return textBanner;
87+
}
88+
if (this.fallbackBanner != null) {
89+
return this.fallbackBanner;
90+
}
91+
return DEFAULT_BANNER;
92+
}
93+
94+
private Banner getTextBanner(Environment environment) {
95+
String location = environment.getProperty(BANNER_LOCATION_PROPERTY, DEFAULT_BANNER_LOCATION);
96+
Resource resource = this.resourceLoader.getResource(location);
97+
try {
98+
if (resource.exists() && !resource.getURL().toExternalForm().contains("liquibase-core")) {
99+
return new ResourceBanner(resource);
100+
}
101+
}
102+
catch (IOException ex) {
103+
// Ignore
104+
}
105+
return null;
106+
}
107+
108+
private String createStringFromBanner(Banner banner, Environment environment, Class<?> mainApplicationClass)
109+
throws UnsupportedEncodingException {
110+
String charset = environment.getProperty("spring.banner.charset", StandardCharsets.UTF_8.name());
111+
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
112+
try (PrintStream out = new PrintStream(byteArrayOutputStream, false, charset)) {
113+
banner.printBanner(environment, mainApplicationClass, out);
114+
}
115+
return byteArrayOutputStream.toString(charset);
116+
}
117+
118+
/**
119+
* Decorator that allows a {@link Banner} to be printed again without needing to
120+
* specify the source class.
121+
*/
122+
private static class PrintedBanner implements Banner {
123+
124+
private final Banner banner;
125+
126+
private final Class<?> sourceClass;
127+
128+
PrintedBanner(Banner banner, Class<?> sourceClass) {
129+
this.banner = banner;
130+
this.sourceClass = sourceClass;
131+
}
132+
133+
@Override
134+
public void printBanner(Environment environment, Class<?> sourceClass, PrintStream out) {
135+
sourceClass = (sourceClass != null) ? sourceClass : this.sourceClass;
136+
this.banner.printBanner(environment, sourceClass, out);
137+
}
138+
139+
}
140+
141+
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@
180180
* @author Tadaya Tsuyukubo
181181
* @author Lasse Wulff
182182
* @author Yanming Zhou
183+
* @author Junhyung Park
183184
* @since 1.0.0
184185
* @see #run(Class, String[])
185186
* @see #run(Class[], String[])
@@ -215,6 +216,8 @@ public class SpringApplication {
215216

216217
private Banner banner;
217218

219+
private SpringApplicationBannerPrinter bannerPrinter;
220+
218221
private ResourceLoader resourceLoader;
219222

220223
private BeanNameGenerator beanNameGenerator;
@@ -559,11 +562,9 @@ private Banner printBanner(ConfigurableEnvironment environment) {
559562
}
560563
ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader
561564
: new DefaultResourceLoader(null);
562-
SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);
563-
if (this.properties.getBannerMode(environment) == Mode.LOG) {
564-
return bannerPrinter.print(environment, this.mainApplicationClass, logger);
565-
}
566-
return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
565+
566+
SpringApplicationBannerPrinter bannerPrinter = Objects.requireNonNullElseGet(this.bannerPrinter, () -> new DefaultSpringApplicationBannerPrinter(resourceLoader, this.banner));
567+
return bannerPrinter.print(environment, this.mainApplicationClass, this.properties.getBannerMode(environment));
567568
}
568569

569570
/**
@@ -1036,6 +1037,16 @@ public void setBannerMode(Banner.Mode bannerMode) {
10361037
this.properties.setBannerMode(bannerMode);
10371038
}
10381039

1040+
/**
1041+
* Sets the {@link SpringApplicationBannerPrinter} used to print the banner. Defaults to
1042+
* {@link SpringApplicationBannerPrinter}.
1043+
*
1044+
* @param bannerPrinter the printer used to print the banner
1045+
*/
1046+
public void setBannerPrinter(SpringApplicationBannerPrinter bannerPrinter) {
1047+
this.bannerPrinter = bannerPrinter;
1048+
}
1049+
10391050
/**
10401051
* Sets if the application information should be logged when the application starts.
10411052
* Defaults to {@code true}.

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplicationBannerPrinter.java

Lines changed: 5 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -16,118 +16,19 @@
1616

1717
package org.springframework.boot;
1818

19-
import java.io.ByteArrayOutputStream;
20-
import java.io.IOException;
21-
import java.io.PrintStream;
22-
import java.io.UnsupportedEncodingException;
23-
import java.nio.charset.StandardCharsets;
24-
25-
import org.apache.commons.logging.Log;
26-
2719
import org.springframework.aot.hint.RuntimeHints;
2820
import org.springframework.aot.hint.RuntimeHintsRegistrar;
2921
import org.springframework.core.env.Environment;
30-
import org.springframework.core.io.Resource;
31-
import org.springframework.core.io.ResourceLoader;
32-
33-
/**
34-
* Class used by {@link SpringApplication} to print the application banner.
35-
*
36-
* @author Phillip Webb
37-
*/
38-
class SpringApplicationBannerPrinter {
39-
40-
static final String BANNER_LOCATION_PROPERTY = "spring.banner.location";
41-
42-
static final String DEFAULT_BANNER_LOCATION = "banner.txt";
43-
44-
private static final Banner DEFAULT_BANNER = new SpringBootBanner();
45-
46-
private final ResourceLoader resourceLoader;
4722

48-
private final Banner fallbackBanner;
23+
public interface SpringApplicationBannerPrinter {
4924

50-
SpringApplicationBannerPrinter(ResourceLoader resourceLoader, Banner fallbackBanner) {
51-
this.resourceLoader = resourceLoader;
52-
this.fallbackBanner = fallbackBanner;
53-
}
25+
String BANNER_LOCATION_PROPERTY = "spring.banner.location";
5426

55-
Banner print(Environment environment, Class<?> sourceClass, Log logger) {
56-
Banner banner = getBanner(environment);
57-
try {
58-
logger.info(createStringFromBanner(banner, environment, sourceClass));
59-
}
60-
catch (UnsupportedEncodingException ex) {
61-
logger.warn("Failed to create String for banner", ex);
62-
}
63-
return new PrintedBanner(banner, sourceClass);
64-
}
27+
String DEFAULT_BANNER_LOCATION = "banner.txt";
6528

66-
Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {
67-
Banner banner = getBanner(environment);
68-
banner.printBanner(environment, sourceClass, out);
69-
return new PrintedBanner(banner, sourceClass);
70-
}
71-
72-
private Banner getBanner(Environment environment) {
73-
Banner textBanner = getTextBanner(environment);
74-
if (textBanner != null) {
75-
return textBanner;
76-
}
77-
if (this.fallbackBanner != null) {
78-
return this.fallbackBanner;
79-
}
80-
return DEFAULT_BANNER;
81-
}
82-
83-
private Banner getTextBanner(Environment environment) {
84-
String location = environment.getProperty(BANNER_LOCATION_PROPERTY, DEFAULT_BANNER_LOCATION);
85-
Resource resource = this.resourceLoader.getResource(location);
86-
try {
87-
if (resource.exists() && !resource.getURL().toExternalForm().contains("liquibase-core")) {
88-
return new ResourceBanner(resource);
89-
}
90-
}
91-
catch (IOException ex) {
92-
// Ignore
93-
}
94-
return null;
95-
}
96-
97-
private String createStringFromBanner(Banner banner, Environment environment, Class<?> mainApplicationClass)
98-
throws UnsupportedEncodingException {
99-
String charset = environment.getProperty("spring.banner.charset", StandardCharsets.UTF_8.name());
100-
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
101-
try (PrintStream out = new PrintStream(byteArrayOutputStream, false, charset)) {
102-
banner.printBanner(environment, mainApplicationClass, out);
103-
}
104-
return byteArrayOutputStream.toString(charset);
105-
}
106-
107-
/**
108-
* Decorator that allows a {@link Banner} to be printed again without needing to
109-
* specify the source class.
110-
*/
111-
private static class PrintedBanner implements Banner {
112-
113-
private final Banner banner;
114-
115-
private final Class<?> sourceClass;
116-
117-
PrintedBanner(Banner banner, Class<?> sourceClass) {
118-
this.banner = banner;
119-
this.sourceClass = sourceClass;
120-
}
121-
122-
@Override
123-
public void printBanner(Environment environment, Class<?> sourceClass, PrintStream out) {
124-
sourceClass = (sourceClass != null) ? sourceClass : this.sourceClass;
125-
this.banner.printBanner(environment, sourceClass, out);
126-
}
127-
128-
}
29+
Banner print(Environment environment, Class<?> sourceClass, Banner.Mode bannerMode);
12930

130-
static class SpringApplicationBannerPrinterRuntimeHints implements RuntimeHintsRegistrar {
31+
class SpringApplicationBannerPrinterRuntimeHints implements RuntimeHintsRegistrar {
13132

13233
@Override
13334
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/builder/SpringApplicationBuilder.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.springframework.boot.BootstrapRegistry;
3535
import org.springframework.boot.BootstrapRegistryInitializer;
3636
import org.springframework.boot.SpringApplication;
37+
import org.springframework.boot.SpringApplicationBannerPrinter;
3738
import org.springframework.boot.WebApplicationType;
3839
import org.springframework.boot.convert.ApplicationConversionService;
3940
import org.springframework.context.ApplicationContext;
@@ -337,6 +338,11 @@ public SpringApplicationBuilder bannerMode(Banner.Mode bannerMode) {
337338
return this;
338339
}
339340

341+
public SpringApplicationBuilder bannerPrinter(SpringApplicationBannerPrinter bannerPrinter) {
342+
this.application.setBannerPrinter(bannerPrinter);
343+
return this;
344+
}
345+
340346
/**
341347
* Sets if the application is headless and should not instantiate AWT. Defaults to
342348
* {@code true} to prevent java icons appearing.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot;
18+
19+
import org.junit.jupiter.api.Test;
20+
import org.junit.jupiter.api.extension.ExtendWith;
21+
22+
import org.springframework.boot.Banner.Mode;
23+
import org.springframework.boot.testsupport.system.CapturedOutput;
24+
import org.springframework.boot.testsupport.system.OutputCaptureExtension;
25+
import org.springframework.context.support.GenericApplicationContext;
26+
import org.springframework.core.io.Resource;
27+
import org.springframework.core.io.ResourceLoader;
28+
import org.springframework.mock.env.MockEnvironment;
29+
30+
import static org.assertj.core.api.Assertions.assertThat;
31+
32+
/**
33+
* Tests for {@link DefaultSpringApplicationBannerPrinter}.
34+
*
35+
* @author Moritz Halbritter
36+
* @author Junhyung Park
37+
*/
38+
@ExtendWith(OutputCaptureExtension.class)
39+
class DefaultSpringApplicationBannerPrinterTests {
40+
41+
@Test
42+
void shouldUseUtf8(CapturedOutput capturedOutput) {
43+
ResourceLoader resourceLoader = new GenericApplicationContext();
44+
Resource resource = resourceLoader.getResource("classpath:/banner-utf8.txt");
45+
SpringApplicationBannerPrinter printer = new DefaultSpringApplicationBannerPrinter(resourceLoader,
46+
new ResourceBanner(resource));
47+
printer.print(new MockEnvironment(), DefaultSpringApplicationBannerPrinterTests.class, Mode.LOG);
48+
assertThat(capturedOutput).containsIgnoringNewLines("\uD83D\uDE0D Spring Boot! \uD83D\uDE0D");
49+
}
50+
51+
}

0 commit comments

Comments
 (0)