![[Spring Boot] @Profile 은 어떻게 동작할까? [Spring Boot] @Profile 은 어떻게 동작할까?](http://t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png)
스프링 부트 프로젝트를 진행하다 보면 운영, 개발, 로컬 등등 프로파일 별로 환경 변수를 다르게 설정해서 사용하게 됩니다.
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"); // 해당 빈을 못 찾는다.
});
}
![[Spring Boot] @Profile 은 어떻게 동작할까? - 빈 등록 테스트 해보기 [Spring Boot] @Profile 은 어떻게 동작할까? - 빈 등록 테스트 해보기](https://blog.kakaocdn.net/dn/DRzeo/btsECuf1cnO/WzoIActUJJEiUdFgU0DGO0/img.png)
해당 코드를 테스트하면 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 조합이 가장 대표적으로 사용하는 방식이다.
클래스의 존재로 해당 기술의 사용 여부를 확인하고, 직접 추가한 커스텀 빈 구성의 존재를 확인해서 자동 구성의 빈 오브젝트를 이용할지 최종 결정한다.
'Spring Boot' 카테고리의 다른 글
[Spring] @RequestBody를 Controller에서 받기 전 변환하기 - Interceptor(HttpServletRequestWrapper), RequestBodyAdvice 사용하기 (2) | 2024.05.18 |
---|---|
MongoDB 테스트하기(TestContainer 및 memory DB) (0) | 2024.01.08 |
[GitHub Actions + Spring Boot + Nginx + Slack Notification] 무중단 배포 CI/CD 구축하기 (0) | 2023.11.22 |
제어할수 없는 것에 의존하지 않기(테스트하기 좋은 코드) (1) | 2023.11.05 |
QueryDSL 다중 DB 설정하기 (0) | 2023.06.27 |
댓글