@Value와 @ConfigurationProperties
@Value
와 @ConfigurationProperties
는 외부 설정 값들인 Property들을 주입받아 사용할 수 있게 해주는 어노테이션입니다. 굳이 Java 내부에서 직접 사용하지 않고 외부 설정 파일에 있는 Property 값들을 주입받아서 사용하는 이유는 크게 2가지 이유가 있습니다.
관리하기 편하다.
특정 값(value)이 여러 곳에서 사용할 경우 관리하기 편리해집니다. 예를 들어 실행 환경, 서버 설정 값, 중요 key값들을 예로 들 수 있습니다. Property의 값만 변경하면 사용하는 곳에도 적용되어 유지보수가 용이해집니다.
중요한 정보를 노출시키지 않는다
특정 API들을 사용하기 위해서는 누구인지 식별하기 위한 API key가 필요합니다. 예를 들어 AWS의 access-key, secret-key 그리고 DB에 접속하기 위한 URL, username, password 등이 있으며 이러한 정보들은 프로젝트 내부에서 하드코딩을 하게 되면 외부에 노출되어 탈취될 위험이 있습니다. 실제로 AWS key 값들을 제대로 관리하지 않아 수 백만원씩 요금을 청구당하는 일이 빈번하게 생기고 있습니다. 따라서 이러한 중요 정보들은 외부에 노출되지 않도록 외부 설정 값들을 관리하는 yml, properties와 같은 파일에 따로 관리하게 되면 해당 파일을 유실하지 않는 이상 정보가 노출되지 않기 때문에 안전하게 관리할 수 있다는 장점이 있습니다
예제로 사용된 코드는 Github에서 확인할 수 있습니다.
1. @Value
placeholder를 이용하여 Property의 단일 값을 가져오는 방법입니다. ${my.app.myProp}
와 같은 스타일 속성 자리 표시자를 이용하여 해당 Property 값을 가져올 수 있습니다.
(SpEL 방식으로 값을 가져오는 방법이 있지만 해당 방법은 글 목적에 벗어나기 때문에 생략하겠습니다.)
1-1. @Value 어노테이션 사용 방법
yml
aws:
s3:
access-key: access-key value
secret-key: secret-key value
bucket: bucket value
test code
@SpringBootTest
@ActiveProfiles("local")
public class PropertyTest {
@Value("${aws.s3.access-key}")
private String accessKey;
@Value("${aws.s3.secret-key}")
private String secretKey;
@Value("${aws.s3.bucket}")
private String bucket;
@DisplayName("@Value 어노테이션 사용")
@Test
void property_test1() {
Assertions.assertThat(accessKey).isEqualTo("access-key value");
Assertions.assertThat(secretKey).isEqualTo("secret-key value");
Assertions.assertThat(bucket).isEqualTo("bucket value");
}
}
위와 같이 @Value
어노테이션을 사용하게 되면 yml에 있는 설정 값들을 가져올 수 있습니다. 하지만 @Value
에는 몇 가지 문제점이 있습니다.
1-2. @Value 어노테이션의 문제점
타입 안정성 문제
Property 값이 true or false 인 경우 boolean으로 값을 가져오거나 String으로 값을 가져올 수 있습니다. 따라서 해당 Property 값이 boolean으로 사용해야 하는지 String으로 사용해야 하는지 주석을 달지 않으면 명확하지 않아 사용 시 혼동이 올 수 있습니다. 마찬가지로 1234와 같은 숫자로 이루어진 Property 값 또한 int, long, String 중 어느 타입으로 사용해야 하는지 모호성이 발생하게 됩니다.
test-property:
value1: true # boolean, String type 모두 가능
value2: 1234 # int, String type 모두 가능
@SpringBootTest
@ActiveProfiles("local")
public class PropertyTest {
@Value("${test-property.value1}")
private boolean booleanValue;
@Value("${test-property.value1}")
private String stringValue1;
@Value("${test-property.value2}")
private int intValue;
@Value("${test-property.value2}")
private String stringValue2;
@DisplayName("@Value 어노테이션 타입 안정성")
@Test
void property_test2() {
// boolean, String type으로 값을 가져올 수 있다.
Assertions.assertThat(booleanValue).isTrue();
Assertions.assertThat(stringValue1).isEqualTo("true");
// int, String type으로 값을 가져올 수 있다.
Assertions.assertThat(intValue).isEqualTo(1234);
Assertions.assertThat(stringValue2).isEqualTo("1234");
}
}
RelaxeyBinding이 적용되지 않음
@Value
는 RelaxeyBinding이 적용되지 않습니다. 일반적으로 외부 파일에서는 Kebab Case를 사용하고 자바에서는 Camel Case를 사용하는데 자바 코드 내부에서 외부 파일에 있는 Property를 가져오기 위해서는 Kebab Case를 사용해야 하는 단점이 있습니다.
대표 명명 규칙
- Camel Case : 단어의 첫 글자를 대문자로 표기
ex) userName - Snake Case : 단어 사이에 언더스코어(_)를 사용
ex) user_name - Kebab_Case : 단어 사이에 하이픈(-)을 사용
ex) user-name
유지보수의 어려움
@Value
를 사용하여 특정 Property를 여러 곳에서 사용하게 되면 유지보수에 어려움이 발생하게 됩니다. 단순히 Property의 Value 값만 변경될 때는 문제가 없지만 Property의 prefix가 변경이 된다면 해당 property를 사용하는 @Value 어노테이션을 모두 찾아 key의 경로 값을 변경해줘야 하기 때문에 유지보수가 어려워진다는 단점이 있습니다.
2. @ConfigurationProperties
Property에 있는 값들을 클래스로 바인딩하기 위해 사용하는 어노테이션입니다. @Value
와 차이점은 다음과 같습니다.
@Value
는 Property 단일 값만 가져올 수 있었지만@ConfigurationProperties
를 사용하게 되면 여러 개의 Property 값들을 가져올 수 있다.- 관련 Property들을 하나의 클래스에서 관리하기 때문에 유지보수가 용이하다.
- RelaxeyBinding이 적용되어 네이밍 규칙이 달라도 유연하게 값을 바인딩할 수 있다.
- 타입 안정성
2-1. @ConfigurationProperties 어노테이션 사용 방법
yml
aws:
s3:
access-key: access-key value
secret-key: secret-key value
bucket: bucket value
Property 값들을 바인딩할 클래스를 만들어 줍니다.
@ConfigurationProperties(prefix = "aws.s3") // prefix
@Getter
@RequiredArgsConstructor
public class S3Property {
private final String accessKey;
private final String secretKey;
private final String bucket;
}
@ConfigurationProperties
를 적용한 클래스를 사용하기 위해서는 Bean으로 등록해야 하므로 스캔 대상을 지정해줘야 합니다. 저 같은 경우는 따로 설정 클래스를 만들어 스캔 대상을 지정해 줬습니다.
@Configuration
@EnableConfigurationProperties({S3Property.class})
public class AWSPropertyConfiguration {
}
@EnableConfigurationProperties
으로 @ConfigurationProperties
를 적용한 클래스를 스캔 대상으로 지정해주지 않으면 Not registered via @EnableConfigurationProperties, marked as Spring component, or scanned via @ConfigurationPropertiesScan와 같은 에러가 발생합니다. 따라서 해당 에러가 발생했다면 스캔 대상을 제대로 지정해 줬는지 확인해 보시길 바랍니다.
이제 필요한 곳에서 주입받아 사용하면 됩니다.
@SpringBootTest
@ActiveProfiles("local")
public class PropertyTest {
@Autowired
private S3Property s3Property;
@DisplayName("@ConfigurationProperties 어노테이션 사용")
@Test
void property_test3() {
Assertions.assertThat(s3Property.getAccessKey()).isEqualTo("access-key value");
Assertions.assertThat(s3Property.getSecretKey()).isEqualTo("secret-key value");
Assertions.assertThat(s3Property.getBucket()).isEqualTo("bucket value");
}
}
2-2. Property 값들을 바인딩하는 다양한 방법 1
Property의 prefix가 다를 경우 2개의 바인딩 클래스를 만들어서 사용할 수 있습니다.
yml
aws:
s3:
access-key: access-key value
secret-key: secret-key value
bucket: bucket value
region:
region-static: region-static value
s3 바인딩 클래스
@ConfigurationProperties(prefix = "aws.s3") // prefix
@Getter
@RequiredArgsConstructor
public class S3Property {
private final String accessKey;
private final String secretKey;
private final String bucket;
}
region 바인딩 클래스
@ConfigurationProperties(prefix = "aws.region")
@Getter
@RequiredArgsConstructor
public class RegionProperty {
private final String regionStatic;
}
aws 바인딩 클래스
@ConfigurationProperties(prefix = "aws")
@Getter
@RequiredArgsConstructor
public class AWSProperty {
private final S3Property s3;
private final RegionProperty region;
}
여기서 주의할 점은 각 필드명은 해당 Property의 prefix와 일치해야 합니다.
설정 관련 클래스
@Configuration
@EnableConfigurationProperties({S3Property.class, AWSProperty.class, RegionProperty.class})
public class AWSPropertyConfiguration {
}
Test 코드
@SpringBootTest
@ActiveProfiles("local")
public class PropertyTest {
@Autowired
private AWSProperty awsProperty;
@DisplayName("@ConfigurationProperties 어노테이션 사용 - 여러개 Property")
@Test
void property_test4() {
Assertions.assertThat(awsProperty.getS3().getAccessKey()).isEqualTo("access-key value");
Assertions.assertThat(awsProperty.getS3().getSecretKey()).isEqualTo("secret-key value");
Assertions.assertThat(awsProperty.getS3().getBucket()).isEqualTo("bucket value");
Assertions.assertThat(awsProperty.getRegion().getRegionStatic()).isEqualTo("region-static value");
}
}
2-3. Property 값들을 바인딩하는 다양한 방법 2
두 번째 방법으로는 Inner Class를 사용하여 바인딩할 수 있습니다.
yml
s3:
access-key: access-key value
secret-key: secret-key value
bucket:
stage: stage-bucket value
production: production-bucket value
s3 바인딩 클래스
@ConfigurationProperties(prefix = "s3")
@Getter
@RequiredArgsConstructor
public class S3InnerProperty {
private final String accessKey;
private final String secretKey;
private final BucketProperty bucket;
@Getter
@RequiredArgsConstructor
public static class BucketProperty {
private final String stage;
private final String production;
}
}
이 역시 필드명은 Property의 prefix와 일치해야 합니다.
설정 관련 클래스
@Configuration
@EnableConfigurationProperties({S3InnerProperty.class})
public class AWSPropertyConfiguration {
}
'Backend > Spring' 카테고리의 다른 글
[Spring] Bean Validation과 Custom Validation (0) | 2023.07.08 |
---|---|
[Spring] HTTP 요청 데이터 바인딩(@RequestParam, @ModelAttribute, @RequestBody, @RequestHeader) (0) | 2023.07.02 |
[Spring] HTTP 요청 매핑 어노테이션(@RequestMapping) (0) | 2023.07.01 |