상세 컨텐츠

본문 제목

동일한 타입의 Bean을 조건에 따라 생성하는 방법이 없을까?

프로젝트/performance-tracker

by seungpang 2023. 1. 31. 20:39

본문

반응형

performance-tracker라는 토이프로젝트를 하고 있다. 이 프로젝트는 테스트코드들의 각각 쿼리가 얼마나 걸리고 어떤 쿼리들이 실행되고 있는지 확인하기 위한 목적이 크다.

 

짧게나마 프로젝트 소개를 했으니 제목에도 언급했던 것처럼 동일한 타입의 Bean을 조건에 따라 생성해야 하는 이유에 대해 먼저 말하고자 한다.

 

위의 그림에서 보면 지금 현재 ContextManager가 존재하는데 이 클래스가 하는 역할은 테스트코드들이 실행되고 그 결과를 모으는 역할을 한다. 밑에 결과를 보면 EndpointMethod냐에 따라 각각 보여주는 내용이 다르다는 것을 알 수 있다.

 

EndpointContextManager
MethodContextManager

 

그리고 Descriptor는 그 결과들에 나타내는 것이다. 결과인데 각각 LogJson 형식이 존재한다. 그렇다면 어떤 ContextManager를 사용할지는 뭐로 선택되는 걸까?


어떤 ContextManager를 쓸 건지는 아래와 같이 테스트 코드에 명시해 줘서 쉽게 알 수 있다.

 

@SpringBootTest @PerformanceTracker(context = ContextType.ENDPOINT) class DescriptorTest {
private Class<? extends ContextManager> determineContextManager(ExtensionContext context) {
    PerformanceTracker annotation = context.getRequiredTestClass().getAnnotation(PerformanceTracker.class);
    return annotation.context().getContextManagerClass();
}

 

현재 ContextManager는 PerformanceTracker에 어떤 애노테이션에 따라 그에 맞는 ContextManager를 반환해준다.

 

하지만 어떤 Descriptor를 사용할지는 실행할 때 결정되기 때문에 우리가 쉽게 아는 빈의 우선순위를 정해주는 @Primary나 @Qualifier로 해결할 수 없다. 그럼 어떤 방식으로 해결해야 할까?


그것은 바로 조건부 애노테이션이다.

 

스프링 부트에는 많은 라이브러리에 대한 자동 설정 클래스들이 준비되어 있는데 이러한 작업들을 스프링 부트 조건부 애노테이션인 @Conditional을 사용해서 처리한다.

 

해당 Conditional 애노테이션은 종류도 다양하고 쓰임새도 많다.

간단히 요약하자면 아래와 같이 나타낼 수 있다.

 

애노테이션 설명
@ConditionalOnWebApplication 프로젝트가 웹 애플리케이션이면 Bean 등록
@ConditionalOnBean 해당 Bean이 존재하면 자동 설정 등록
@ConditionalOnMissingBean 해당 Bean이 존재하지 않으면 자동설정 등록
@ConditionalOnClass 해당 클래스가 존재하면 자동설정 등록
@ConditionalOnMissingClass 해당 클래스가 클래스 패스에 존재하지 않으면 Bean
@ConditionalOnResource 해당 자원(file 등)이 존재하면 자동설정 등
@ConditionalOnProperty 설정한 프로퍼티가 존재하면 자동설정 등록

 

하지만 이중에서 ConditionalOnProperty을 사용해서 쉽게 해결할 수 있었다.

테스트 코드를 실행할 때 어떤 Json형식일지 log형식일지 정하는 것이 당연하다고 생각했기 때문에 아래와 같이 구현했다.

 

@Component
@ConditionalOnProperty(value = "com.morak.performance-tracker.format", havingValue = "log", matchIfMissing = true)
public class LoggingDescriptor implements Descriptor 

@Component
@ConditionalOnProperty(value = "com.morak.performance-tracker.format", havingValue = "json")
public class JsonDescriptor implements Descriptor

 

@ConditionalOnProperty의 value와 havingValue의 값이 일치해야 동작한다.

실제로 사용한다면 아래와 같은 형식이 되는 것이다.

 

com.morak.performance-tracker.format=json
com.morak.performance-tracker.format=log

 

그리고 마지막으로 matchIfMissing이 있는데 만약에 프로퍼티를 아무것도 정의하지 않았을 때 해당 설정을 로드한다. 따라서 프로퍼티에 아무것도 작성하지 않았다면 해당 코드에서는 LoggingDescriptor가 생성되는 것이다.

 

이처럼 Conditional 애노테이션을 통해 쉽게 동적으로 빈을 생성할 수 있다는 것을 알았다. 앞으로 다른 코드들도 Conditional 조건으로 빈을 생성하는 것도 나쁘지 않다는 생각을 했다.

 

끄읏~