Spring Boot banner로 알아보는 Composite 패턴

nmy6452·2025년 12월 13일

spring-boot

목록 보기
1/1
post-thumbnail

개발자의 길을 걸어가고 있는 사람이라면 누구라도 들어본 그 디자인 패턴
그럼에도 불구하고, 취업을 위해 이름만 달달 외웠을 뿐, 실제 실무에서 이 패턴들을 상황에 맞게 적절히 적용하며 코드를 설계하는 개발자는 상위 10%에 불과한 것 같습니다.

오늘은 Spring Boot의 실행 과정을 알아보던 중에 Spring Banner를 출력하는 부분에서 익숙한 패턴을 찾아 서술해보고자 합니다.

SpringBoot 프로젝트를 실행하면 출력되는 제일 처음 Console에 출력되는 Spring의 배너입니다.

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

Spring에서 출력한 배너를 만들어주는 사이트도 있더라구요

Composite 패턴이란,

위키피디아에 정의된 Composite(복합체) 패턴의 정의를 살펴보면 아래와 같습니다.

컴포지트 패턴(Composite pattern)이란 객체들의 관계를 트리 구조로 구성하여 부분-전체 계층을 표현하는 패턴으로, 사용자가 단일 객체와 복합 객체 모두 동일하게 다루도록 한다.

쉽게 예를들어 설명하자면, 플레이리스트의 노래를 재생하는것에 비유할 수 있습니다.

우리는 유튜브 뮤직이나 멜론 같은 스트리밍 사이트에서 노래를 들을때 음악을 재생한다는 한가지 행동으로 노래를 들을 수 있습니다
그것이 한곡의 노래던지, 내가 좋아하는 가수의 앨범이던지, 내가 좋아하는 노래를 모아둔 플레이리스트던지 관계없이 말입니다.

위 예시와 같이, Composite 패턴을 적용한다면, 그 객체의 행위에 대해서 객체가 단일 객체(Leaf)던, 복합 객체(Composite)던 관계 없이 특정 행위를 실행할 수 있습니다.

Spring Banner 출력 과정

정확한 표현은 아닐지 모르지만 이해를 쉽게하기 위해서 간략하게 그 출력 과정을 풀어보자면 다음과 같습니다.
1. Spring 기동 과정에서 배너 출력을 담당하는 객체 생성 및 출력 호출
2. 배너 출력 가능한 Banner 객체 탐색
3. BannerprintBanner() 매서드 호출하여 배너출력

이때 Banner객체는 Composite 또는 단일 객체로 이루어져있을 수 있으며 Composite 형태로 되어 있을때를 가정하여 작성한 예시입니다.

자세히보면 배너 출력을 담당하는 SpringApplicationBannerPrinter에서는 단순히 printBanner() 매서드를 1회 호출했음에도 Banners Composite 객체에서 내부적으로 가지고 있는 모든 BannerprintBanner()를 순회하며 호출하고 있는 모습을 볼 수 있습니다.

1. Spring 기동 과정에서 배너 출력을 담당하는 객체 생성 및 출력 호출

Spring은 기동과정에서 SpringApplication객체 내부의printBanner() 매서드를 실행하여 배너 출력을 SpringApplicationBannerPrinter객체에 위임합니다.
그리고 SpringApplicationBannerPrinter객체의 print()매서드 내부에서 실행 가능한 Banner를 찾아 출력하게됩니다.

/**
 * Spring Boot 실행시 SpringApplication.java에서 실행되는 배너 출력 매서드
 * */
	private Banner printBanner(ConfigurableEnvironment environment) {
		if (this.bannerMode == Banner.Mode.OFF) {
			return null;
		}
		ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader
				: new DefaultResourceLoader(null);
		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);
	}

2. 배너 출력 가능한 Banner 객체 탐색

SpringApplicationBannerPrinterprint() 매서드를 보면 getBanner()매서드를 통해서 가져온 banner를 출력하고 있는데,
여기서 Banner가 바로 Composit 패턴이 적용된 객체이며 getBanner()로 가져온 Banner는 단일객체일 수도 복합객체일 수도 있습니다.

/**
 * SpringApplicationBannerPrinter.java에서 배너를 출력하는 매서드
 * */
	Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {
		Banner banner = getBanner(environment);
		banner.printBanner(environment, sourceClass, out);
		return new PrintedBanner(banner, sourceClass);
	}

Spring은 내부적으로 개발자가 등록한 배너를 모두 찾아서 일급 객체인 banners에 등록하여 리턴하며, 만약 그 어떠한 배너도 찾지 못했을 경우에는 단일객체로 기본 배너(SpringBootBanner)를 반환하게됩니다.

/**
 * SpringApplicationBannerPrinter.java에서 배너를 초기화하는 매서드
 * */
	private Banner getBanner(Environment environment) {
		Banners banners = new Banners();
		banners.addIfNotNull(getImageBanner(environment));
		banners.addIfNotNull(getTextBanner(environment));
		if (banners.hasAtLeastOneBanner()) {
			return banners;
		}
		if (this.fallbackBanner != null) {
			return this.fallbackBanner;
		}
		// SpringBootBanner 리턴
		return DEFAULT_BANNER;
	}
/**
 * Leaf를 담당하는 배너 인터페이스
 * */
@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

	}

}
/**
 * Composite 패턴이 적용된 배너 복합체 (일급 컬렉션)
 * */
 private static class Banners implements Banner {

		private final List<Banner> banners = new ArrayList<>();

		void addIfNotNull(Banner banner) {
			if (banner != null) {
				this.banners.add(banner);
			}
		}

		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);
			}
		}

	}

참고자료

💠 복합체(Composite) 패턴 - 완벽 마스터하기

profile
하꼬 개발자

0개의 댓글