퍼사드(fasade) 패턴이란
- 여러 라이브러리, 프레임워크 또는 클래드들의 복합한 집합을 단순화시켜주는 인터페이스를 제공하는 구조의 디자인 패턴
대표적인 예로는 스프링 부트의 slf4j 가 있다.
퍼사드 패턴 적용의 장점
- 대표적으로 퍼사드 패턴이 적용된 프레임워크인 slf4j는 log4j, log4j2, logback 등 을 코드 수정 없이 적합한 프레임워크를 slf4j 만의 인터페이스를 통해 info, error 등의 로그 기능을 통합하여 단순하게 사용할 수 있게 해준다.
따라서 slf4j 를 사용하여 Spring Boot 의 로깅 시스템을 구현한다는 가정을 했을경우 log4j2를 사용도중 심각한 보안 이슈가 터졌을 경우 자바 또는 코틀린 코드 수정이 없이 설정 파일 추가만으로 logback 으로 빠르게 이전할 수 있다.
https://kanoos-stu.tistory.com/49 (log4j2 보안문제)
참고로 slf4j 는 interface 이므로 구현이 된 로깅 프레임워크(logback, log4j) 등 없이 단독으로 사용할 수는 없다.
slf4j 를 사용하여 로그를 남기는 방법
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyClass {
private static final Logger logger = LoggerFactory.getLogger(MyClass.class);
public void doSomething() {
logger.info("Doing something");
//logger.debug("Doing something");
//logger.warn("Doing something");
//logger.error("Doing something");
// ...
}
}
slf4j 가 퍼사드패턴을 통해 logback bind 받는 과정(코드)
1. 자바 또는 코틀린 코드로 slf4j 의 LoggerFactory.getLogger(string) 정적 메서드를 통해 Logback Logger 호출
val logger: Logger = LoggerFactory.getLogger(this::class.java)
2. org.slf4j 패키지 LoggerFactory 클래스의 387 줄의 getLogger 메서드에서 getLogger(string) 내부 메서드 호출
public static Logger getLogger(Class<?> clazz) {
Logger logger = getLogger(clazz.getName());
if (DETECT_LOGGER_NAME_MISMATCH) {
Class<?> autoComputedCallingClass = Util.getCallingClass();
if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {
Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),
autoComputedCallingClass.getName()));
Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");
}
}
return logger;
}
3. org.slf4j 패키지 LoggerFactory 클래스의 361 줄의 getLogger 메서드에서 getILoggerFactory() 내부 메서드 호출
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
4. org.slf4j 패키지 LoggerFactory 클래스의 412 줄의 getILoggerFactory 메서드에서
logback 의존성이 추가되어 초기화 되었다면(SUCCESSFUL_INITIALIZATION)
StaticLoggerBinder.getSingleton().getLoggerFactory() 메서드 호출
초기화가 되지 않았다면 (UNINITIALIZED) performInitialization() -> bind() 메서드를 통해 binding 되어 slf4j 의 구현체로 logback 이 설정되게 된다.
public static ILoggerFactory getILoggerFactory() {
if (INITIALIZATION_STATE == UNINITIALIZED) {
synchronized (LoggerFactory.class) {
if (INITIALIZATION_STATE == UNINITIALIZED) {
INITIALIZATION_STATE = ONGOING_INITIALIZATION;
performInitialization();
}
}
}
switch (INITIALIZATION_STATE) {
case SUCCESSFUL_INITIALIZATION:
return StaticLoggerBinder.getSingleton().getLoggerFactory();
case NOP_FALLBACK_INITIALIZATION:
return NOP_FALLBACK_FACTORY;
case FAILED_INITIALIZATION:
throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
case ONGOING_INITIALIZATION:
// support re-entrant behavior.
// See also http://jira.qos.ch/browse/SLF4J-97
return SUBST_FACTORY;
}
throw new IllegalStateException("Unreachable code");
}
5. org.slf4j.impl (logback) 패키지의 getLoggerFactory() 메서드에서 logback 내부에 설정된 Logger 를 반환한다.
public ILoggerFactory getLoggerFactory() {
if (!initialized) {
return defaultLoggerContext;
}
if (contextSelectorBinder.getContextSelector() == null) {
throw new IllegalStateException("contextSelector cannot be null. See also " + NULL_CS_URL);
}
return contextSelectorBinder.getContextSelector().getLoggerContext();
}
위의 코드에서 logback 이 slf4j 에게 바인딩 되는 부분은 logback 라이브러리에 org.slf4j.impl 패키지에 생성된 StatucBinder 클래스들을 생성해놨기 때문이다.