
개발자의 길을 걸어가고 있는 사람이라면 누구라도 들어본 그 디자인 패턴
그럼에도 불구하고, 취업을 위해 이름만 달달 외웠을 뿐, 실제 실무에서 이 패턴들을 상황에 맞게 적절히 적용하며 코드를 설계하는 개발자는 상위 10%에 불과한 것 같습니다.
오늘은 Spring Boot의 실행 과정을 알아보던 중에 Spring Banner를 출력하는 부분에서 익숙한 패턴을 찾아 서술해보고자 합니다.
SpringBoot 프로젝트를 실행하면 출력되는 제일 처음 Console에 출력되는 Spring의 배너입니다.
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/
위키피디아에 정의된 Composite(복합체) 패턴의 정의를 살펴보면 아래와 같습니다.
컴포지트 패턴(Composite pattern)이란 객체들의 관계를 트리 구조로 구성하여 부분-전체 계층을 표현하는 패턴으로, 사용자가 단일 객체와 복합 객체 모두 동일하게 다루도록 한다.
쉽게 예를들어 설명하자면, 플레이리스트의 노래를 재생하는것에 비유할 수 있습니다.
우리는 유튜브 뮤직이나 멜론 같은 스트리밍 사이트에서 노래를 들을때 음악을 재생한다는 한가지 행동으로 노래를 들을 수 있습니다
그것이 한곡의 노래던지, 내가 좋아하는 가수의 앨범이던지, 내가 좋아하는 노래를 모아둔 플레이리스트던지 관계없이 말입니다.
위 예시와 같이, Composite 패턴을 적용한다면, 그 객체의 행위에 대해서 객체가 단일 객체(Leaf)던, 복합 객체(Composite)던 관계 없이 특정 행위를 실행할 수 있습니다.
정확한 표현은 아닐지 모르지만 이해를 쉽게하기 위해서 간략하게 그 출력 과정을 풀어보자면 다음과 같습니다.
1. Spring 기동 과정에서 배너 출력을 담당하는 객체 생성 및 출력 호출
2. 배너 출력 가능한 Banner 객체 탐색
3. Banner의 printBanner() 매서드 호출하여 배너출력
이때 Banner객체는 Composite 또는 단일 객체로 이루어져있을 수 있으며 Composite 형태로 되어 있을때를 가정하여 작성한 예시입니다.

자세히보면 배너 출력을 담당하는 SpringApplicationBannerPrinter에서는 단순히 printBanner() 매서드를 1회 호출했음에도 Banners Composite 객체에서 내부적으로 가지고 있는 모든 Banner의 printBanner()를 순회하며 호출하고 있는 모습을 볼 수 있습니다.
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);
}
Banner 객체 탐색SpringApplicationBannerPrinter 의 print() 매서드를 보면 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);
}
}
}