개발을 하다보면 시스템에 필요한 상수가 존재합니다. ex)비밀키
이를 속성 파일(application.yml)이나 환경 변수로 빼서 관리할 수 있습니다.
외부에서 필요한 상수를 관리하면 Java 소스코드를 재컴파일 하지 않아도 값을 변경할 수 있고 실행 환경에 따라 다른 값을 설정할 수도 있습니다.
저는 위와 같은 경우 보통 다음과 같이 사용했습니다.
@Value("${prefix.value}")
private String value;
하지만 위 방식은 문제가 있습니다.
@Value 어노테이션 동작 방식
어떤 문제가 있는지 알아보기 전에 잠시 @Value 어노테이션의 동작 방식을 알아봅시다.
@Value 어노테이션은 Spring IoC 컨테이너의 DI 과정과 비슷한 규칙으로 동작합니다.
필드에 작성한 어노테이션은 DI의 필드 인젝션처럼 Java Reflecation API를 사용합니다.
필드 인젝션은 다음과 같은 과정을 거칩니다.
- Bean을 생성합니다.
- Bean 초기화 과정에서 @Value가 붙은 필드를 찾습니다. - Java Reflecation API 사용
- 해당 필드를 지정된 표현식 또는 값으로 설정합니다.
따라서 위 코드는 다음과 같은 문제가 발생합니다.
상수로 선언 불가능
첫 번째는 상수로 선언 불가능입니다.
저희가 원하는 것은 상수를 설정하는 것이기 때문에 필드에 final을 붙이고 싶습니다.
위 코드를 다음과 같이 변경해보겠습니다.
@Value("${prefix.value}")
private final String value;
그러면 다음과 같은 컴파일 에러를 볼 수 있습니다.
Variable 'value' might not have been initialized
어찌보면 당연한 결과입니다. 컴파일러 입장에서는 final 변수는 초기화되지 않았기 때문입니다.
테스트 용이성이 떨어짐
두 번째는 테스트 용이성 떨어지는 것입니다.
위에서 필드 인젝션은 Java Reflection API을 사용해 설정한다고 설명했습니다.
이는 접근 제한자가 제약을 우회하여 직접 필드에 값을 설정하는 것을 의미합니다.
만약 필드에 @Value 어노테이션이 붙은 클래스를 테스트하려면 테스트 코드에서도 리플렉션을 사용하여 값을 설정해야 합니다.
이는 테스트 코드를 작성하기 힘들게 만듭니다.
해결 방법
필드 주입 대신 생성자 주입을 사용하여 해결할 수 있습니다.
생성자 주입은 다음과 같은 장점이 있습니다.
- final 필드를 생성자에서 초기화할 수 있다.
- IoC 컨테이너가 없어도 테스트 코드에서 원하는 값을 생성자로 전달할 수 있다.
위의 문제점이 모두 해결됐습니다!
그래서 코드를 다음과 같이 수정할 수 있습니다.
class Hello {
private final String value;
public Hello(@Value("${prefix.value}") String value) {
this.value = value;
}
}
필드 주입 대신 생성자 주입을 사용하자!
참고
'Spring' 카테고리의 다른 글
@WebMvcTest, Spring Security 적용 후 403 에러 (0) | 2024.03.27 |
---|---|
테스트 네이밍 (0) | 2023.08.26 |