참조 : https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-external-config
외부설정(External Config)
외부 설정이란 어플리케이션에서 사용하는 여러 설정 값들을
어플리케이션 밖(혹은 안)에 정의할 수 있는 기능을 말한다.
사용할 수 있는 외부 설정
- properties
- YAML
- 환경 변수
tags: - 커맨드 라인 아규먼트
properties
일단 백문이 불여일견. 예제를 통해 접해본다.
앞에서 살펴본 application.properties
는 스프링 부트가
어플리케이션을 구동할때 자동으로 로딩하는 파일 이름이다.
클래스 경로에 이 이름의 속성 파일이 있으면 스프링 부트가
자동으로 해당 파일을 읽어준다.
속성 파일의 두는 곳은 몇가지 우선 순위가 존재한다.
(숫자가 낮을 수록 우선 순위가 높다.)
- 시작할 때 –spring.config.location에서 지정한 파일.
- 현재 디렉토리 바로 아래의 config 디렉토리에 있는 파일.
- 현재 디렉토리에있는 파일.
- 클래스 경로 바로 아래의 config 패키지에 있는 파일.
- 클래스 경로 바로 아래에 있는 파일.
숫자가 낮을 수록 우선 순위가 높다.
1 | tester.name=GeunChang Ahn |
1 |
|
다양한 방법으로 프로퍼티를 적용할 수 있기 때문에
우선순위가 존재한다.
프로퍼티 우선 순위
- (유저 홈) 디렉토리에 있는 spring-boot-dev-tools.properties
- 테스트에 있는 @TestPropertySource
- @SpringBootTest 애노테이션의 properties 애트리뷰트
- 커맨드 라인 아규먼트
- SPRING_APPLICATION_JSON (환경 변수 또는 시스템 프로티) 에 들어있는 프로퍼티
- ServletConfig 파라미터
- ServletContext 파라미터
- java:comp/env JNDI 애트리뷰트
- System.getProperties() 자바 시스템 프로퍼티
- OS 환경 변수
- RandomValuePropertySource
- JAR 밖에 있는 특정 프로파일용 application properties
- JAR 안에 있는 특정 프로파일용 application properties
- JAR 밖에 있는 application properties
- JAR 안에 있는 application properties
- @PropertySource
- 기본 프로퍼티 (SpringApplication.setDefaultProperties)
application.properties
는 15번에 해당한다.
이전장에서 배운 아규먼트를 사용해서 테스트 해보자.
커맨드 라인 아규먼트는 4번 우선순위를 가지고 있다.
1 | java -jar target/springboot-applicationtest-0.0.1-SNAPSHOT.jar --user.name=Ahn |
이렇게 하면 properties에 설정한 값과 다르게
아규먼트로 준 값으로 덮어쓰여진 것을 확인할 수 있다.
위에 @Value
어노테이션 예제에서 ${tester.name}
을${user.name}
으로 바꾸면 현재 윈도우 계정의 이름이 찍힌다.
이는 OS환경변수가 10번 우선순위라 15번의 프로퍼티 파일보다
우선시 되기 때문이다.
테스트에선 2번,3번 우선 순위를 사용할 수 있다.
기본적으로 Environment를 통해서 가져올 수 있다.
1 | package me.rkaehdaos; |
위에서 environment에서 프로퍼티를 가져오는 부분을 잘 기억한다.
테스트 용도의 프로퍼티가 필요한 경우 어떻게 하면 좋을까
간단하게 테스트리소스에(src/test/resources) application.properties를
작성하면 된다.
1 | tester.name=tester |
위 소스에서 test밑의 프로퍼티 값을 바꾸고 test를 실행하면
바꾼 프로퍼티값으로 셋 되기때문에 테스트를
통과하지 못하는 것을 볼 수 있다.
이는 컴파일 순서에 관한 것으로
일단 main에 있는 소스가 컴파일 되고 그다음에
test에 있는 것들이 컴파일되면서 클래스패스에 등록이 된다.
이때 테스트 용으로 덮어 씌여지는 것이다.
3번째 우선 순위를 사용해 본다.
1 |
|
우선순위 3번째로 properties 애트리뷰트로 주어진 값이 우선시된다.
더 우선시 되는것은 별도의 TestPropertySource
어노테이션이다.
1 |
|
@TestPropertySource
는 우선순위 2번이므로 3번째를 덮어 쓴다.
value 애트리뷰트(value는 생략 가능)로 resource의 위치를 지정할 수 있다.
1 |
|
앞에서처럼 application.properties
를 소스와 테스트 2개를 관리할 경우
덮어 씌워지기때문에 소스쪽에서 100개가 필요하면 테스트에도 100개를 채워야한다.
이러면 둘다 관리가 어려워진다.(테스트에 필요한 것까지 해서 100개가 넘어갈 수도)
이 경우 위에서처럼 다른 이름으로 테스트에 프로퍼티파일(test.properties)
을 만들고 여기에 달라진 것들만 추가한후 @TestPropertySource
로 테스트 프로퍼티
를 지정하면 어떻게 될까? 소스쪽이 컴파일 되서 들어가고 테스트쪽에서 프로퍼티
읽어드릴때 같은 키값의 프로퍼티는 테스트쪽으로 덮어쓰여지게 된다.
덮어 쓰여지는 이유는 위에서 봤다시피 jar 안의 properties파일은 15번 우선 순위이고
@TestPropertySource에 지정하면 2번 우선 순위가 되기 때문이다
application.properties 우선 순위
application.properties라는 같은 이름의 파일은 4군데에 놓을 수 있다.
만약 4군데에 다 놓는다면?
여기에도 우선순위가 있어서 높은것이 낮은것을 덮어 쓰게 된다.
- file:./config/
- file:./ (Root 혹은 Jar파일을 실행하는 위치)
- classpath:/config/
- classpath:/ (현재 리소스에 놓은 것은 4순위)
어차피 프로퍼티 우선순위 15번 안에서 싸움인 것을 인지하자.
타입-세이프 프로퍼티 @ConfigurationProperties
외부 설정이 많은 경우에 같은 키의 프로퍼티를 묶어서
하나의 빈으로 등록하는 방법이 있다.
1 | package me.rkaehdaos; |
저렇게 하면 인텔리J에서 경고가 뜬다.
Spring Boot Configuration annotation processor not found in classpath
application.properties
를 ide로 작성하다보면
스프링 관련된 값이 자동 완성되는 것을 볼 수 있다.
이 자동완성은 메타 정보를 기반으로 하는데
이 메타정보를 완성해주는 플러그인을 추가하라는 알림이 뜨는 것이다.
spring-boot-configuration-processor
jar를 사용함으로써
손쉽게 @ConfigurationProperties
의 아이템들에 대한
고유 메타정보를 생성할 수 있다.
다음과 같이 의존성을 추가한다.
1 | <dependency> |
이제 빌드 할때 @ConfigurationProperties
메타정보를
생성해주어서 application.properties에서 해당 아이템에 대한
자동완성을 사용할 수 있다.
현재는 @ConfigurationProperties
과 클래스 멤버 변수가
바인딩 되어 있을 뿐이며 아직은 사용할 수 없다.
원래는 다음과 같이 사용할 수 있으나
1 |
|
@EnableConfigurationProperties
은 기본적으로 자동 등록 되어있다.
즉 어노테이션은 이미 설정되어 있는 상태니 생략해도 된다.
단지 properties클래스만 빈 설정을 하면 된다.
이렇게 빈등록이 되면 다른빈에 주입할 수가 있다.
1 | package me.rkaehdaos; |
1 |
|
이전의 @Value
등으로 가져올때와의 차이는 무엇일까?
일단은 type safe하지 않다.
@Value
의 value에 오타를 칠 수도 있고 …
하지만 이제 getter 메소드를 사용함으로써
type safe할 수 있다.Meta-data 지원 여부
위에서 봤듯이@ConfigurationProperties
은 메타데이터를 지원함으로써
application.properties(or yml)생성시 자동완성을 지원한다.Relaxed binding(융통성 있는 바인딩) 지원 여부
스프링 부트는 @ConfigurationProperties
빈에
Environment
프로퍼티를 바인딩 할때 융통성 있는(Relaxed) 규칙을 적용해서
Environment프로퍼티 이름과 빈 프로퍼티 이름이 완벽하게 같지 않아도 된다.
대소문자 차이(PORT vs port),
dash(-)구분이랄지(context-path vs contextPath)등을 알아서 구분한다.
다음 4가지는 모두 동일하게 정상 작동하게 된다.
- context-path : kebab-case (properties,yml에서 사용권장)
- context_path : Underscore notation (properties,yml의 다른 포맷)
- contextPath : Standard Camel case
- CONTEXTPATH : Upper case (시스템 환경 변수에서 권장되는)
@Value
는 이걸 지원하지 않는다.
- SpEL 지원 여부
@ConfigurationProperties
는 SpEL을 지원하지 않는다.
@Value
가 SpEL 표현을 지원하지만 프로퍼티 파일에서는 처리되지 않음을 명심하자.
properties 파일이 type safe 해봤자..
써드 파티 설정
@ConfigurationProperties
를@Component
클래스에 사용했던 것처럼
public @Bean
메소드에도 사용할 수 있다.
이는 개발자 컨트롤 밖의 써드 파티 컴포넌트들의
프로퍼티를 바인드 하고자 할때 유용하다.
Environment
프로퍼티로 빈을 환경 설정하기 위해서
빈 등록에 @ConfigurationProperties
를 추가한다.
1 | //`@Bean`이 붙었으니 `@Component`를 붙일 순 없다. |
@Bean vs @Component
@Bean :
메소드를 타겟으로 한다.
메소드 반환 타입의 클래스의 인스턴스가가 빈이 된다.
builder와 setter를 사용하여 사용자가 주고 싶은 값을 반영해서 생성된 인스턴스를
스프링에게 관리하고 맡기는 것.
class 소스를 접할 수 없는 라이브러리는 당연히@Bean
으로 밖에 접근할 수 없다.@Component:
클래스를 타겟으로 한다.
사용자가 작성한 클래스 파일을 스프링에 맡겨서 스프링이 알아서
이 클래스 타입의 인스턴스를 생성하여 빈 등록 한다.
이 차이를 알면 지금 이것도 알 수 있다.
프로퍼티 타입 컨버전
스프링 부트는 외부 어플리케이션 프로퍼티를 @ConfigurationProperties
bean들에게
바인딩 할때 알맞는 타입으로 컨버전하려고 시도한다.
프로퍼티는 사실 문서안에서는 타입이 없고 전부 문자열이다.
이를 스프링이 기본적으로 제공하는 컨버전 서비스를 통해서
타입 컨버전 되서 들어가는 것이다.
만약 커스텀한 타입 컨버전이 필요한 경우
- (
conversionService
로 명명(named)된)conversionService
빈을 제공하거나 - (
CustomEditorConfigurer
빈을 통한) 커스텀 프로퍼티 에디터를 제공하거나 - (
@ConfigurationPropertiesBinding
를 빈 정의에 사용한)
커스텀Converters
를 제공할 수 있다.
duration 컨버전
스프링 부트에는 이외에도 스프링 부트만이 제공하는 독특한 컨버전이 존재하는데
그 중 하나가 duration이다.
dur : 지속하다
during : 지속하는 사이
duration : 지속되는 기간, 존속 기간.
java.time.Duration
의 Duration 클래스는 자바 8부터 존재하며 특정시간A와
특정시간 B 사이의 시간 차이를 초나 nano초의 시간의 양으로 모델링 한다.
java.time.Duration
프로퍼티를 노출(expose)하면은
어플리케이션 프로퍼티에서 다음과 같은 형식이 가능하다.
- 일반적인
long
형식
(@DurationUnit
으로 단위 지정하지 않으면 기본단위로 밀리초) java.util.Duration
에 의해 사용되는 표준 ISO-8601 형식- value와 단위가 합쳐져서 사람이 더 읽기 쉬운 형식
(ex. 10s = 10초)
1 |
|
이렇게 하면 프로퍼티에 ssessionTimeout
이 없으면 기본값 30s를 가지게 되며ssessionTimeout
가 있으면 그값으로 주입이 된다.
1 | tester.ssessionTimeout=18 |
이렇게 하면 ide에서 경고가 뜬다 “Value ‘18 ‘ is not a valid duration”라며18s
나 18ns
등의 입력을 권장하게 되지만 괜찮다.
위에서 어노테이션으로 단위를 SECONDS
로 지정했기 되기때문에
18초로 들어가서 값을 출력해보면 PT18S
로 18s 출력이 됨을 확인할 수 있다.
어노테이션을 쓰지 않고 단위 suffix를 붙일지
어노테이션을 쓰고 값만 취급할지는 상황에 따라서 활용이 가능할 것 같다.
고정된 프로퍼티라면 어노테이션 없이 쓰는 것이 좋을 것같고
숫자만 받아오는 상황이라면 단위처리를 위해 어노테이션을 써서 처리하는 것이 좋을 것
같다.
프로퍼티 값 데이터 검증 (Validate)
@ConfigurationProperties
클래스들이 스프링의 @Validated
어노테이션이 붙으면
스프링 부트가 데이터 검증(validate)을 시도한다.
유효성 검사 ,데이터 검증 여러 말이 많지만 Validation으로 그냥 많이 쓰이기도..
스프링부트의 Validation 전에 기초부터 싹 정리
1. 자바의 Validation 스펙
만약 데이터검증이 프레젠테이션, 비지니스, 데이터 엑세스 각 레이어마다 거의 동일한
내용의 검증 로직이 구현된다면 그것은 중복이고 낭비가 심하며 각 계층 별 구현된
로직들의 불일치로 인하여 오류가 발생하기도 쉽다
이를 해결하기 위해 데이터 검증을 위한 로직을 도메인 모델 자체에 묶어서 표현하는
방법이 있다. 실제 코드로 표현되면 너무 장황하고 복잡하기에 자바에서는 Bean validation
이라는 어노테이션을 데이터 검증을 위한 메타데이터로 사용하는 방법을 제시한다
Bean Validation 명세는 현재 2.0 (JSR 380) 까지 나와있음
3. 스프링의 Validation
참조자료:
자바의 Bean validation 은 검사기(Validator)를 사용해야하고
그로부터 반환된 Set<ConstraintViolation<타입>>
을 받아 사용해야 한다
Spring 에서는 Java Bean validation 을 완벽하게 지원하면서,
이런 Validator를 직접 사용하지 않고 AOP 와 같은 방식으로 더 편리하게
validation 할 수 있는 장치들을 제공하고 있다
자바의 validation @Valid
를 사용해서 타겟을 지정했는데 이는
명세 1.1(JSR-349)에서 추가된 validation group
의 어떤 그룹에서 validation이
일어날지 표현할 수 없다. validation group(검증 그룹)은 제약조건에 지정하는 그룹이다.
이를 이용해서 중복코드를 없애고 다른 검증방법을 선택할 수 있다.
예를 들어 일반 사용자 폼과 관리자용 폼이 같은 모델 클래스로 바인딩 되지만
다른방식의 Validation이 필요한다던지의 사례이다.
1 | public interface User {} |
검증 그룹은 위처럼 내용없는 인터페이스로 정의한다. 일종의 마커 인터페이스
스프링에서는 검사기의 Validator.validate()
를 사용해 유효성을 진행하지 않고
AOP같은 방법을 사용하기 때문에 어노테이션에 그룹세트를 명시적으로 지정해야 한다.
따라서 스프링은 @Validated
라는 어노테이션을 따로 제공한다.
1 |
@Validated
대신 @Valid
를 이용하면 groups로 지정한 검증 그룹은 무시되고
모든 제약조건이 일괄 적용된다.
스프링의 메소드에 대한 validation
AOP를 사용하여 메소드 실행시 validation을 한다.MethodValidationPostProcessor
를 빈을 정의하고,
필요한 클래스 혹은 메소드에 @Validated 애노테이션을 추가하면 됩니다.
1 |
|
제약 조건 위반시 ConstraintViolationException
이 발생한다.
스프링 부트의
ValidationAutoConfiguration
을 보면 이미MethodValidationPostProcessor
빈이 정의 되어 있다.
Spring Validator
스프링 내부에서 사용하는 독자적인 Validator 인터페이스가 존재한다.
–> org.springframework.validation.Validator
Spring MVC의 Validation
데이터 바인딩 시점에 실행하게 된다.WebMvcConfigurationSupport
나WebFluxConfigurationSupport
를 보면ConfigurableWebBindingInitializer
생성시 Validator가 주입됨을 알 수 있다.
자 다시 돌아와서
위에서 봤던 @Validated
을 @ConfigurationProperties
클래스에 사용이 가능하다.@Validated
와 함께 프로퍼티클래스에 제약조건 어노테이션을 달면 된다.
그리고 위의 스프링 메소드에서도 AOP기능으로 작동 되었듯이
써드파티의 @Bean
빈에 @ConfigurationProperties
사용할때도 역시@Validated
를 사용하는 것이 가능하다.
1 |
|
프로퍼티의 이름을 위의 제약과 틀리게 하면 알파벳 하나만 넣자.
그럼 다음과 같은 에러가 발생한다.
1 | 2018-10-31 15:04:48.380 ERROR 26696 --- [main] o.s.b.d.LoggingFailureAnalysisReporter : |
이렇게 어여쁜 에러메세지가 나올 수 있는 이유는 지난 번에 살펴봤던
FailureAnalyzer
덕분이다.
정리
이렇게 프로퍼티를 밖에서 사용할때는 이렇게 하나의 키값으로 모아서
프로퍼티 클래스에서 validation까지 처리하자.이러면 해당 키값에 대한
자동완성까지 지원된다.
@Value
보다 @ConfigurationProperties
를 이용하는 것이 훨씬 좋다.
Related POST
- [Spring Boot] 13. Spring REST Client
- [Spring Boot] 12. Spring Security
- [Spring Boot] 11. Spring Data
- [Spring Boot] 10. 스프링 웹 MVC-2: Spring HATOAS, CORS
- [Spring Boot] 9. 스프링 웹 MVC-1:
- [Spring Boot] 8. Spring-Boot-Devtools
- [Spring Boot] 7. 테스트(Testing)
- [Spring Boot] 6. 로깅(Logging)
- [Spring Boot] 5. Profiles
- [Spring Boot] 4. 스프링부트 외부설정
- [Spring Boot] 3. 스프링부트 핵심기능
- [Spring Boot] 2. 스프링부트 이해
- [Spring Boot] 1. 스프링부트 시작