Spring Boot

[Spring Boot] @Profile 은 어떻게 동작할까?

수수한개발자 2024. 2. 7.
728x90

 

 

스프링 부트 프로젝트를 진행하다 보면 운영, 개발, 로컬 등등 프로파일 별로 환경 변수를 다르게 설정해서 사용하게 됩니다.

 

profile 설정 방법

application.yml

spring:
  profiles:
    default: local
---
spring:
  config:
    activate:
      on-profile: local
---
spring:
  config:
    activate:
      on-profile: dev
---
spring:
  config:
    activate:
      on-profile: prod

 

 

default 일 때 local로 설정.

--- 로 분기하여 각 프로파일일 때 환경 변수를 따로 설정할 수 있습니다.

 

@Profile

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({ProfileCondition.class})
public @interface Profile {
    String[] value();
}

 

해당 어노테이션은 스프링 부트 프로젝트의 Profile을 읽어와서 프로파일 별로 빈을 등록할 수 있게 도와줍니다.

 

@Configuration
public class ProfileConfig {

    @Bean
    @Profile("local")
    public String local() {
        return "local";
    }

    @Bean
    @Profile("dev")
    public String dev() {
        return "dev";
    }
}

 

ProfileConfig는 Profile이 local 일 때는 local이라는 이름의 빈을 등록하고, dev일 때는 dev라는 이름의 빈을 등록하게 되는 단순한 예제 코드 자바 클래스입니다.

 

빈 등록 테스트 해보기

@Test
void profileTest() {
    ApplicationContextRunner contextRunner = new ApplicationContextRunner();
    contextRunner.withUserConfiguration(ProfileConfig.class)
            .withInitializer(context -> context.getEnvironment().setActiveProfiles("dev"))
            .run(context -> {
                assertThat(context).hasSingleBean(String.class);
                assertThat(context.getBean(String.class)).isEqualTo("dev"); // String 타입의 빈을 가져와 값을 확인한다.
                assertThat(context).doesNotHaveBean("local"); // 해당 빈을 못 찾는다.
            });
}

 

 

해당 코드를 테스트하면 dev일때 local이라는 빈을 찾을 수 없다는 테스트가 통과됩니다.

 

그러면 어떻게 스프링 부트는 @Profile 어노테이션을 보고 빈을 등록할지, 등록하지 않을지 결정하는 걸까요?

이 과정을 이해하기 위해서는 @Conditional이라는 어노테이션을 이해해야 합니다.

 

@Conditional

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
    Class<? extends Condition>[] value();
}

 

이 어노테이션은 @Profile 에도 사용됐던 어노테이션입니다.

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({ProfileCondition.class}) // 여기에 사용 됨
public @interface Profile {
    String[] value();
}

 

@Conditional 어노테이션은 Condition이라는 인터페이스를 구현한 구현체에서 조건에 따라 빈을 등록할지, 말지 구분하는 역할을 합니다.

 

ProfileCondition

ProfileCondition 클래스를 살펴보겠습니다.

class ProfileCondition implements Condition {
    ProfileCondition() {
    }

    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
        if (attrs != null) {
            Iterator var4 = ((List)attrs.get("value")).iterator();

            Object value;
            do {
                if (!var4.hasNext()) {
                    return false;
                }

                value = var4.next();
            } while(!context.getEnvironment().acceptsProfiles(Profiles.of((String[])value)));

            return true;
        } else {
            return true;
        }
    }
}

 

1. context.getEnvironment() : 현재 애플리케이션의 환경 정보를 가져옵니다.
2. Profiles.of((String [])value) : value를 문자열 배열로 변환하고, 이를 사용하여 Profiles 객체를 생성합니다. Profiles는 특정 프로파일이 활성화되어 있는지 확인하는 데 사용됩니다.
3. acceptsProfiles(Profiles) : 현재 환경이 생성된 Profiles 객체에 정의된 프로파일을 수용하는지 확인합니다. 프로파일이 활성화되어 있으면 true를 반환하고, 그렇지 않으면 false를 반환합니다.

 

따라서, 이 코드는 주어진 value에 대한 프로파일이 현재 환경에서 활성화되어 있는지 확인합니다.

-> local일때는 

 

따라서 현재 스프링 부트의 환경의 Profile을 dev로 실행시키면

@Profile("local") 이 붙은 객체는 while문에서 걸리지 않아 false를 리턴하게 되고

@Profile("dev") 일 때는  while문이 true여서 true를 반환하게 되어 빈 등록이 되어 테스트가 통과하게 됩니다.

 

 

스프링 부트는 자동 구성 정보들을 이러한 메커니즘을

 

Class Conditions

@ConditionalOnClass

@ConditionalOnMissingClass

  • @Configuration 클래스 레벨에서 클래스가 존재하는지, 라이브러리가 존재하는지 체크하여 빈 등록을 한다.
  • @Bean 메서드에도 적용 가능하다, 단 클래스 레벨의 검증 없이 메서드에만 적용하면 불필요한 @Configuration 클래스가 등록되니 클래스 레벨에서 사용하는 것을 우선해야 한다.

 

 

Bean Conditions

@ConditionalOnBean

@ConditionalOnMissingBean

 

  •  빈의 존재 여부를 기준으로 포함 여부를 결정한다.
  • 빈의 타입 또는 이름을 지정할 수 있다.
  • 컨테이너에 등록된 빈 정보를 기준으로 체크하기 때문에 자동 구성 사이에 적용하려면 @Configuration 클래스의 적용 순서가 중요하다. 개발자가 커스텀 빈 구성 정보가 자동 구성 정보보다 우선시하기 때문에 이 상황에서는 안전하다.

 

@configuration 클래스 레벨의 @ConditionalOnClass와 @Bean 메서드 레벨의 @ConditionalOnMissingBean 조합이 가장 대표적으로 사용하는 방식이다.

클래스의 존재로 해당 기술의 사용 여부를 확인하고, 직접 추가한 커스텀 빈 구성의 존재를 확인해서 자동 구성의 빈 오브젝트를 이용할지 최종 결정한다.

728x90

댓글