[Spring] @Value 어노테이션 (설정 값 분리의 필요성)
개발을 하다보면 DB에 연결해야하는 정보나 외부 API 주소 등 메타 정보를 관리해야할 때가 있다.
이러한 정보들을 클래스 파일에 직접 넣을 수도 있지만, properties나 yml로 분리해서 관리할 것을 권장하고 있다.
이유를 알아보자.
1. 환경에 따라 유연한 값 설정 가능
환경에 따라 다른 값을 자바 코드로만 설정하려면 번거롭고 중복되는 코드가 많아진다.
그래서 설정값은 별도의 properties나 yml 파일을 사용하여 분리하고, 자바 코드에서는 환경에 맞는 설정값을 불러오도록 설정할 수 있다.
2. 초기값 설정 가능
불러오는 설정 값이 초기값을 지정할 수 있는데, 이를 통해 설정값이 불러지지 않은 경우에 대비할 수 있다.
즉, 초기값을 설정함으로써 안전한 개발을 할 수 있다.
3. 불필요한 컴파일 줄이기
값이 클래스 안에 직접 설정되어 있다면 값이 수정될 때마다 소스코드가 변하게 되므로, 다시 컴파일을 해야 한다.
하지만 설정값을 분리하고 @Value로 참조하게 한다면 불필요한 컴파일 없이 주입되는 값을 변경할 수 있다.
값을 클래스에 설정하지 말고 @Value로 참조하여 분리시키는 이유를 알아봤다.
그럼 코드에 어떻게 적용할 수 있을까?
우선 @Value로 정보를 가져오는 방법은 PropertyPlaceHolderConfigurer을 통해 ${}을 사용하는 방식과, SpEL을 통해 #{}을 사용하는 방식이 있다.
1. PropertyPlaceHolderConfigurer
PropertyPlaceHolderConfigurer는 빈 팩토리 후처리기로 빈 설정 메타정보가 모두 준비됐을 때 빈 메타정보 자체를 조작하기 위해 사용한다.
다음과 같이 사용할 수 있다.
jwt:
secret: ${token.secret}
access-token-expiration-time: ${token.access-token-expiration-time}
refresh-token-expiration-time: ${token.refresh-token-expiration-time}
: 옆의 ${}은 환경변수로 설정한 것이다.
@Value("${token.access-token-expiration-time}")
private String accessTokenExpirationTime;
@Value("${token.refresh-token-expiration-time}")
private String refreshTokenExpirationTime;
@Value("${token.secret}")
private String tokenSecret;
yml에 작성된 키 값을 @Value ${} 안에 넣어주면 Spring이 PropertyPlaceHolderConfigurer를 통해 초기화 작업 중에 해당 값을 실제 값으로 치환한다.
하지만 이 방법은 대체할 위치를 치환자로 지정하고 별도의 후처리기가 값을 변경해주기를 기다리기 때문에 수동적인 방법이다.
초기 빈 메타정보에는 ${token.secret} 등과 같은 문자열이 저장되어 있다.
만약 스프링 컨테이너가 설정 파일에서 대체할 적절한 키 값을 찾지 못 한다면, 문자열이 그대로 남아있게 되는 것이다.
그래서 치환자의 값이 변경되지 않더라도 예외가 발생하지 않는다.
위의 문제때문에, Spring 3부터 SpEL을 지원하며 능동 변환을 권장하고 있다.
2. SpEL
SpEL을 통해 다른 빈 오브젝트에 접근할 수 있는 표현식을 이용하여 원하는 프로퍼티에 쉽게 접근이 가능해 값을 능동적으로 가져온다.
SpEL은 #{} 안에 표현식을 넣는데, token.secret이라는 표현식은 이름이 token인 secret 프로퍼티를 의미한다.
SpEL은 다른 빈 프로퍼티에 접근할 수 있고, 메소드 호출이 가능하며 다양한 연산을 지원해 클래스 정보에도 접근이 가능하다.
심지어, 생성자를 호출해서 객체를 생성할 수도 있다.
yml형식은 같고, 클래스에 다음과 같이 사용한다.
@Value("#{environment['token.access-token-expiration-time']}")
private String accessTokenExpirationTime;
@Value("#{environment['token.refresh-token-expiration-time']}")
private String refreshTokenExpirationTime;
@Value("#{environment['jwt.secrett']}")
private String tokenSecret;
environment라는 이름의 빈으로부터 해당 키에 해당하는 값을 읽어온다.
여기서 가져오는 빈은 Properties나 yml타입 등의 빈이 될 수 있다.
SpEL은 오타와 같은 실수가 있을 때 에러 검증이 가능하다.
그래서 이름을 잘못 적는 실수가 있어도 예외가 발생하지 않는 SpEL 사용을 권장하는 것이다.
@Value("#{environment['token.secret'] == null " +
"? '기본 토큰 시크릿 정보'" +
": environment['token.secret']}")
private String tokenSecret;
이런 식으로 사용할 수도 있다.
매번 PropertyPlaceHolderConfigurer 방법을 사용해왔었는데, 간혹 이름을 잘못 적어서 적용이 안 될 때가 있었다.
앞으로는 PropertyPlaceHolderConfigurer 보단 SpEL 방식을 사용해야겠다.