안녕하세요. J4J입니다.
이번 포스팅은 properties에 담긴 환경 변수를 클래스로 사용하는 방법에 대해 적어보는 시간을 가져보려고 합니다.
들어가기에 앞서
환경 변수를 클래스로 사용할 수 있는 방법들이 버전 별로 다른 설정을 해줘야 합니다.
제가 테스트를 위해 사용한 버전은 다음과 같으니 글을 보실 때 참고해 주시길 바랍니다.
- boot 3.2.2
- java 17
환경 변수 사용하는 기본적인 방법
spring에서 properties에 담긴 환경 변수를 사용하는 경우는 profile 관리 목적과 암호화 키 같이 외부에 노출되지 않는 변수를 사용하는 목적으로써 많이 활용되고 있을 겁니다.
그리고 환경 변수를 작성하는 파일은 보통 application.properties이거나 application.yml을 많이 사용하고 계실 겁니다.
일반적으로 해당 파일들에 담긴 환경 변수 값을 사용할 때 많이들 활용하는 방법은 @Value 어노테이션을 활용하는 겁니다.
@Value를 활용하여 환경 변수 key값을 입력하게 되면 key에 매핑되어 있는 value 값을 코드 내부에서 사용할 수 있게 됩니다.
간단하게 사용 예시를 다음과 같이 작성해 보겠습니다.
[ 1. application.yml 변수 추가 ]
spring:
properties:
data: spring-properties-data
[ 2. 변수 사용 클래스 작성 ]
package com.jforj.propertiesclass.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class PropertiesService {
@Value("${spring.properties.data}")
private String springPropertiesData;
public void print() {
log.info("properties에 담겨 있는 데이터 >>> ".concat(springPropertiesData));
}
}
[ 3. 테스트 코드 작성 ]
package com.jforj.propertiesclass.service;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class PropertiesServiceTest {
@Autowired
private PropertiesService propertiesService;
@Test
void printTest() {
propertiesService.print();
}
}
[ 4. 테스트 결과 확인 ]
위의 코드를 확인해 보면 @Value 어노테이션을 이용하여 환경 변수에 담긴 정보를 활용하는 것을 볼 수 있습니다.
그래서 많은 개발자분들이 환경 변수를 사용할 때 @Value 어노테이션을 활용하는데 @Value를 사용하다 보면 불편한 점들을 많이 경험할 수 있습니다.
예를 들면, @Value에 매핑될 properties key 값을 입력하는 것에 오타가 발생되어 값이 제대로 담기지 않거나 properties key 값이 수정되었는데 디버깅 단계에서 확인이 불가능하여 나중에 이슈를 확인하는 것들이 존재합니다.
이런 불편함을 해결하기 위해 시도해 볼 수 있는 방법은 클래스로 사용하도록 설정하는 것입니다.
properties로 사용될 수 있는 값들을 클래스로 관리되도록 변경하면 매번 서로 다른 클래스에서 변수를 사용할 때 @Value를 이용하여 매핑해 주는 것이 아닌 다른 객체들과 동일하게 클래스 내부 필드 값을 활용할 수 있습니다.
또한 properties key값이 변경되면 클래스 내부 필드 값을 변경해 주기 때문에 디버깅 단계에서 잘못된 점도 바로 확인할 수 있습니다.
이런 장점들을 경험할 수 있지만 단점으로는 클래스로 사용될 수 있도록 추가적인 설정 및 관리가 필요하게 됩니다.
그래서 단점보다 장점이 더 돋보인다고 생각되면 @Value 어노테이션을 사용하지 않고 클래스로 관리될 수 있도록 설정하는 것을 권장드립니다.
Setter를 이용한 클래스 설정
properties를 클래스로 관리하는 방법은 다양하게 존재합니다.
그중 먼저 setter로 설정하는 방법에 대해 다루겠습니다.
setter를 활용하여 위의 코드와 동일한 결과를 만드는 코드를 작성하면 다음과 같이 작성할 수 있습니다.
이미 작성된 코드에서 변경점만 다루도록 하겠습니다.
[ 1. dependency 추가 ]
// build.gradle
dependencies {
// properties 클래스 관리를 위한 processor 추가
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
}
[ 2. properties 클래스 추가 ]
package com.jforj.propertiesclass.properties;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties("spring.properties") // properties or yml 파일의 spring.properties 하위에 있는 변수 값을 필드 값으로 정의할 수 있음
@Getter
@Setter
public class SpringProperties {
private String data; // spring.properties.data
}
[ 3. 변수 사용 클래스 변경 ]
package com.jforj.propertiesclass.service;
import com.jforj.propertiesclass.properties.SpringProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Service
@Slf4j
@RequiredArgsConstructor
public class PropertiesService {
private final SpringProperties springProperties;
public void print() {
log.info("properties에 담겨 있는 데이터 >>> ".concat(springProperties.getData()));
}
}
[ 4. 테스트 결과 확인 ]
생성자를 이용한 클래스 설정
이번엔 생성자를 이용하여 설정하는 방법입니다.
setter를 이용한 방법도 동작하는 데는 크게 문제가 없지만 setter가 있는 다른 객체들과 마찬가지로 의도하지 않게 값을 변경하는 상황이 발생할 수도 있습니다.
그래서 이런 경우 setter 대신 생성자를 활용해 볼 수 있습니다.
생성자를 이용하는 경우도 setter와 동일하게 간단하게 활용해 볼 수 있습니다.
setter 설정 값을 기반으로 변경점만 다루도록 하겠습니다.
[ 1. properties 설정 클래스 추가 ]
// @EnableConfigurationProperties을 사용하는 경우
package com.jforj.propertiesclass.config;
import com.jforj.propertiesclass.properties.SpringProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties({
SpringProperties.class // properties로 관리될 클래스를 각각 등록
})
public class PropertiesConfig {
}
// @ConfigurationPropertiesScan을 사용하는 경우
package com.jforj.propertiesclass.config;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationPropertiesScan("**.properties") // 패키지 구조에서 properties가 포함되는 모든 클래스 등록
public class PropertiesConfig {
}
[ 2. properties 클래스 변경 ]
package com.jforj.propertiesclass.properties;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("spring.properties") // properties or yml 파일의 spring.properties 하위에 있는 변수 값을 필드 값으로 정의할 수 있음
@Getter
@RequiredArgsConstructor // 생성자 활용을 위한 @RequiredArgsConstructor 사용
public class SpringProperties {
private final String data; // spring.properties.data
}
[ 3. 테스트 결과 확인 ]
Record를 이용한 클래스 설정
위에서 setter 대신 생성자를 이용하여 클래스 설정을 하는 가장 큰 이유는 properties 클래스의 불변성을 위한 목적입니다.
어차피 properties에 매핑되는 값은 한번 설정이 되면 변경될 일이 없기 때문에 불변성의 목적으로써 활용하기 적합하기도 합니다.
그러다 보니 spring에서 불변성을 처리하기 위해 요즘 많이 사용되고 있는 record의 활용을 고려하지 않을 수 없습니다.
record를 사용할 경우 더 간단하게 클래스를 구성해 볼 수 있기 때문에 record를 사용할 수 없는 이유가 없다면 record로 구성하는 것을 권장드립니다.
record로 사용하는 경우 코드가 어떻게 작성되는지 확인해 보겠습니다.
생성자 설정을 기반으로 변경점만 다루도록 하겠습니다.
[ 1. properties 클래스 변경 ]
package com.jforj.propertiesclass.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("spring.properties") // properties or yml 파일의 spring.properties 하위에 있는 변수 값을 필드 값으로 정의할 수 있음
public record SpringProperties(
String data // spring.properties.data
) {
}
[ 2. 변수 사용 클래스 변경 ]
package com.jforj.propertiesclass.service;
import com.jforj.propertiesclass.properties.SpringProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Service
@Slf4j
@RequiredArgsConstructor
public class PropertiesService {
private final SpringProperties springProperties;
public void print() {
log.info("properties에 담겨 있는 데이터 >>> ".concat(springProperties.data())); // record 변수 사용 방식으로 변경
}
}
[ 3. 테스트 결과 확인 ]
Validation 추가하기
지금까지 properties를 클래스로 관리될 수 있는 다양한 설정들을 확인해 봤습니다.
하지만 여기서 한 가지 더 고려하면 좋은 것이 validation을 활용하는 것입니다.
validation을 활용해야 하는 이유는 클래스로 관리되는 경우 주입되는 값이 없더라도 서버 동작에 전혀 문제가 발생되지 않기 때문입니다.
값이 없더라도 서버 동작은 정상적으로 이루어지기 때문에 비즈니스 로직이 흘러가는 과정에서 properties 값이 없는 이유로 예상치 못한 side effect를 경험하게 됩니다.
그래서 validation을 사용하여 서버가 동작될 때 변수 값에 대한 유효성 검증이 되도록 관리하면 더 효율적으로 환경 변수를 사용해 볼 수 있습니다.
validation 설정을 하는 방법은 다음과 같으며 validation 처리에 더 많은 정보를 알고 싶으시면 [SpringBoot] Spring Validation을 이용한 유효성 검증하기를 참고해주시면 됩니다.
record 설정을 기반으로 변경점만 다루도록 하겠습니다.
[ 1. dependency 추가 ]
// build.gradle
dependencies {
// 유효성 검증을 위한 validation 추가
implementation 'org.springframework.boot:spring-boot-starter-validation'
}
[ 2. properties 클래스 변경 ]
package com.jforj.propertiesclass.properties;
import jakarta.validation.constraints.NotBlank;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
@ConfigurationProperties("spring.properties") // properties or yml 파일의 spring.properties 하위에 있는 변수 값을 필드 값으로 정의할 수 있음
@Validated // 클래스 내부 변수값 유효성 검증을 하도록 설정 추가
public record SpringProperties(
@NotBlank // 값이 null, 빈 문자열, 공백으로만 이루어진 문자열인 경우 exception 발생
String data // spring.properties.data
) {
}
[ 3. 서버 동작 확인 ]
properties 클래스에 담겨야 하는 값을 다음과 같이 주석 처리를 해보겠습니다.
// application.yml
#spring:
# properties:
# data: spring-properties-data
그리고 서버를 구동시키면 다음과 같이 exception이 발생하며 서버가 동작되지 않는 것을 확인할 수 있습니다.
이상으로 properties에 담긴 환경 변수를 클래스로 사용하는 방법에 대해 간단하게 알아보는 시간이었습니다.
읽어주셔서 감사합니다.
'Spring > SpringBoot' 카테고리의 다른 글
[SpringBoot] Spring Security를 이용하여 인증/인가 처리하기 (2) - MVC와 Session에 활용하기 (0) | 2024.02.15 |
---|---|
[SpringBoot] Spring Security를 이용하여 인증/인가 처리하기 (1) - 개념 및 기본 설정 (0) | 2024.02.13 |
[SpringBoot] Layer별 테스트 코드 작성하기 (3) - Controller 테스트 (2) | 2024.02.07 |
[SpringBoot] Layer별 테스트 코드 작성하기 (2) - Service 테스트 (0) | 2024.02.04 |
[SpringBoot] Layer별 테스트 코드 작성하기 (1) - JPA를 이용한 Repository 테스트 (0) | 2024.02.03 |
댓글