[Spring] 외부 상수 넣어주기
https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config
Spring profile VS Gradle(Maven) profile
Gradle(Maven) profile?
- 빌드 시 어떤 profile에 속하는 리소스 파일을 빌드 결과물에 포함 시킬지 말지를 결정
- 빌드타임 프로파일 설정
- 즉, Gradle(Maven) profile이 더 큰 파일 개념이고, 선택한 파일(e.g., 리소스 파일)을 jar에 포함하거나 포함하지 않거나를 결정하는데 사용함.
- 예를 들어 alpha/resources.yml 파일을 패키징 시 jar에 포함 할거냐 말거냐.
Spring profile? :: @Profile(), spring.profiles.active
- Spring profile은 빌드 결과물 내에서 스프링이 어떤 profile을 사용할지를 결정
- 런타임 프로파일 설정
- 공통으로 사용할
application.yml
을 하나 두고, 빌드 환경에 따라 추가적으로 application-${profile}.yml
을 불러오도록 구성하려면 Spring profile 세팅이 꼭 필요하다. - 빌드시점에 Gradle profile로
application-${profile}.yml
을 포함하도록 하더라도, 스프링에서 여기있는 변수를 불러오는건 별개의 문제이기 때문 - spring.profiles.default도 있는데, 그냥 .active로 설정하는 것이 나아보임. (.default로 세팅하면 @Value(“spring.profiles.active”)로 안가져와지기 때문.)
크게 gradle/maven 컴파일 시 환경 별 resource를 모두 포함할지 말지 / SpringBoot 실행 시 profile을 명시할지 말지에 따라 크게 아래 3가지 방법으로 나눠볼 수 있음.
- 컴파일 할 때 alpha 전용으로 컴파일하고, SpringBoot 실행 시에도 spring.profiles.active 명시해야 하는 방법
- 컴파일 할 때 alpha 전용으로 컴파일하고, SpringBoot 실행 시 spring.profiles.active 명시안해도 되는 방법
- 컴파일 할 때 (alpha, beta, real) resources를 모두 포함해 컴파일하고, SpringBoot 실행 시 spring.profiles.active 명시해서 구분하는 방법
1번 방법 : alpha 전용으로 컴파일, 런타임에 spring.profiles.active 명시
- alpha 전용으로 컴파일 하기 때문에 다른 환경(real)의 resources가 jar에 포함되지 않음. alpha jar가 유출되어도 리얼 key 등은 안전.
- 하지만 별도 key 저장소를 사용하는 경우 리얼 key hash가 유출되어도 그 자체만으로는 의미가 없어 안전하긴 함.
- 환경 별로 바이너리를 따로 컴파일 해야 하는 것은 보통 별로 문제되는 부분은 아님. (알파 베타는 테스트용도 로 소스 브랜치가 달라 어차피 빈번히 빌드해야 하기 때문에)
- SpringBoot 실행 시 profile 명시해야 하는데, 다른 profile로 실행하면 에러 발생. (resource 찾을 수 없음)
2번 방법 : alpha 전용으로 컴파일, 런타임에 spring.profiles.active 명시안함
- alpha 전용으로 컴파일. 1번과 동일.
- application.properties의 spring.profiles.active의 값을 compile time에 확정해서 넣어주는 방식 이기 때문에, SpringBoot 실행 시 profile 명시할 필요 없음.
- 실수 방지 차원에서 1번 방법 보다 나아보임. 어차피 다른 profile로 실행하면 에러날 것이기 때문에.
3번 방법 : 모든 profile resources를 포함해 컴파일, 런타임에 spring.profiles.active 명시
- alpha, beta, real에서 모두 같은 바이너리 사용 가능. SpringBoot 실행 시 profile 명시해서 모드 구분.
- 프로파일 변경 시 굳이 빌드를 다시 할 필요가 없음.
- profile 구분 없이, 어떤 프로파일이든 모든 리소스 파일이 jar 패키징 시 포함되도록 구성하면 되기 때문에 컴파일 타임의 Gradle/Maven profile은 신경쓰지 않아도 됨.
- 테스트 시 리소스 문제로 신경써야 하는 상황이 적음.
- 빌드 시 @ActiveProfiles로 테스트만 다른 프로파일 적용해서 돌린다거나 하는게 가능.
- alpha jar가 유출되면 리얼 key도 유출되는게 단점.
- 별도 key 저장소 사용하지 않고 resouces에 평문으로 key가 저장되어 있는 경우 크리티컬.
- 제일 심플한 방법. gradle에 profile 관련 세팅이 들어갈 필요가 없고, 컴파일 타임에 profile을 신경 쓸 필요 없음.
- 별도 key 저장소 사용하거나 그렇게까지 보안을 신경쓰지 않아도 되는 환경이라면 이렇게 구성하는게 제일 간단.
2번 방법 구성안
Gradle Profile 구성하기
build.gradle.kts
1
2
3
4
5
6
7
8
9
10
11
| val profile = if (project.hasProperty("profile"))
project.property("profile").toString() else "local"
sourceSets {
main {
resources {
srcDirs(listOf("src/main/resources", "src/main/resources-$profile"))
}
}
}
// spring으로 넘어기는 프로파일은 $profile (@Profile 애너테이션 등에서 사용하게 될)
// 리소스를 불러오는 것은 resources-$environment 로 나누는 것도 필요하다면 가능하다.
|
- 여기서 주의할 점
- 같은 파일명의
application.yml
이 각 dir 마다 존재한다면, 가장 마지막에 명시된 dir의 application.yml
만을 사용하게 됨. - 그래서 폴더를
resources
, resources-alpha
등으로 나누더라도 내부에 들어가는 파일 이름을 다르게 지정해 주어야 한다. (application-real.yml)
- 폴더를
resources
(공통리소스), resources-alpha
(환경 별 리소스)로 나누는 이유?- 폴더 분리 안하고
resources/
하위에 application-beta.yml
등을 모두 함께 넣어버리면, 해당 dir의 모든 리소스가 같이 포함되어 jar로 묶인다. - alpha jar가 유출되면 뜯어서 리얼 서버에서 사용하는 Key 등등을 알아낼 수 있음.
- 따라서 폴더를 분리하여 필요한 리소스만 포함해서 빌드하도록 하는 것이 좋다.
- 또, 폴더 분리를 하지 않는 경우 common.yml 같은 별도 프로퍼티 파일을 환경 별로 두기 어려워진다.
- 별도 파일로 두지 못하니…
- application-{profile}.yml에 {profile}용 common.yml의 모든 설정을 넣어 합치거나
- 환경 별로 따로 존재하던 common.yml을 하나로 합치고 파일 내에서 activate.on-profile 사용해서 구분해야 한다.
- 아니면 환경변수로 gradle로부터 {profile}을 넘겨받아 import=common-${profile}.yml 이 가능하도록 만들거나.
- common.yml은 어찌 처리한다 쳐도, logback.xml 같은 환경 별로 이름이 동일한 파일은 어쨌든 폴더 구조로 관리하는게 편하다.
srcDirs, include, exclude 설정을 통해서 모두 resources 하위에 두는 것도 가능은 하지만 조금 번잡스럽다.
Spring Profile 구성하기
https://www.baeldung.com/spring-profiles
application.yml
1
2
3
| spring:
profiles:
active: @env@
|
- 컴파일 타임에 maven profile이 local이었다면, sping profile도 local이 되도록 maven properties @env@를 가져와 세팅
- 빌드 결과물 보면 local로 치환되어 있다.
- 런타임에 어떤 Spring profile을 사용할 것인지 명시 할 필요 없음. (application.yml에 active로 명시되어 있기 때문에)
- 이렇게 설정해줘야
application-local.yml
에 있는 상수도 불러올 수 있다. - 스프링은 프로필로 설정된 문자열에 해당하는
application-${profile}.yml
가 있는지 뒤져보고, 있다면 불러온다. (기본 설정)- 이 경우
application-local.yml
이 있는지 찾아보고, 없으면 넘어가고 있다면 불러옴.
- 이렇게 profile base로 추가적인 리소스를 가져오는 것 이외에, 아예 다른 이름의 프로퍼티를 가져오고 싶다면https://www.baeldung.com/properties-with-spring4.7 참고
maven pom.xml의 변수를 application.yml에 전달
1
2
3
4
5
6
7
8
9
10
11
12
| <profile>
<id>alpha</id>
<properties>
<env>alpha</env>
</properties>
</profile>
...
<resource>
<filtering>true</filtering> <-- 이 설정이 없으면 @env@가 치환되지 않는다.
<directory>src/main/resources</directory>
</resource>
|
build.gradle.kts의 profile 변수를, gradle Task 실행 시 전달
1
2
3
4
| // Test task를 실행할 때 profile 전달
tasks.getByName<Test>("test") {
systemProperty("spring.profiles.active", profile)
}
|
build.gradle의 변수를 application.yml에 전달
https://www.baeldung.com/spring-boot-auto-property-expansion
1
2
3
4
| ## 변환 전
spring:
profiles.active: ${profile} # <- gradle에서 가져올 property
batch.job.name: \${job.name:NONE} # <- gradle에서 가져오지 않을 property
|
1
2
3
4
5
| tasks.processResources {
filesMatching("application.yml") {
expand("profile" to profile)
}
}
|
1
2
3
4
| ## 변환 후 (빌드 결과)
spring:
profiles.active: alpha # <- gradle에서 가져올 property
batch.job.name: ${job.name:NONE} # <- gradle에서 가져오지 않을 property
|
1
2
3
| > Failed to parse template script (your template may contain an error or be trying to use expressions not currently supported): startup failed:
SimpleTemplateScript23.groovy: 1: Unexpected input: '(' @ line 1, column 10.
out.print("""spring:
|
IntelliJ에서 실행할 때?
- intelliJ에서는 Run Configurations에서 설정해줄 것.
- Gradle task(bootRun, Test, …)를 실행할 때 선택할 profile 지정
- 방법1 ) 위의 build.gradle.kts의 profile 변수를 gradle Task 실행 시 전달
- 방법2 ) Before launch: Gradle task에 환경변수 명시
- Gradle task 실행시 선택할 profile을 명시 안하면 Test 돌릴 때 Profile이 default로 설정돼서 실패뜬다.
분명 모든 설정을 잘 했는데 안될 때… 빌드 결과물 resource 확인해보면 프로파일이 제대로 적용되지 않은 경우가 종종 있음
IntelliJ 오류로 언제는 되고 언제는 안되고 그럴 수 있어서 확인 필요.
반대로 application-core.yml을 구성하는 방법은?
- [1] 위 방식은 공통으로 사용할
resources/application.yml
과 선택에 따라 resources-${profile}/application-${profile}.yml
을 불러오는 방법. - [2] 반대로 공통으로 사용할
resources/application-core.yml
을 두고, 선택에 따라 resources-${profile}/application.yml
을 불러오도록 하는 방법도 있다. - 어느 쪽이 되었건
application.yml
이 먼저 로드되고, application-xxx.yml
이 더 늦게 로드 된다. - 같은 이름 상수가 정의되어 있는 경우,늦게 로드 되는 쪽이 overwrite 한다!
- 그래서 [1] 방식을 사용하는 편이 좋아 보인다. 실수로 같은 이름 상수를 정의한 경우에도, 더 specific한 설정으로 덮어 써지므로.
test property 설정 예제
- test/resources에 있는 것은 이걸 사용하고, 없는 것은 main/resources 사용하고 싶은 경우?
- 별도 설정 필요 없음.자동으로 이렇게 되어 있음.
1
2
3
| target // 빌드 결과 dir
ㄴ classes // 2. 1번에서 없으면 여기 리소스를 뒤진다.
ㄴ test-classes // 1. 테스트 시 여기 리소스를 먼저 뒤지고
|
1
2
3
4
5
6
| // 실행 커맨드에 spring profile 옵션도 주어 application.yml의 active profile을 덮어쓰도록 한다.
$ mvn clean test -P test -Dspring.profiles.active=test
// 참고로 install 시 테스트 스킵하는 옵션은
$ mvn clean install -P beta -Dmaven.test.skip=true
|
참고
https://garyj.tistory.com/9
https://perfectacle.github.io/2017/09/23/Spring-boot-gradle-profile/