Issue
플리보따리 서비스에서 SpotifyApi관련 유틸을 만들고있던 과정에 발생한 일이다.
SpotifyConfig 클래스를 리팩토링 하고있었다.
간단히 설명하자면,
- SpotifyApi를 생성할려면 개발자 페이지에서 발급받은 클라이언트 키와 비밀번호를 가지고 있어야 한다.
- 클래스는 하나의 역할을 가진다는 클린코드 철학으로 나는 SpotifyApi 관련 기능들을 SpotifyConfig 내에 맡겼다.
본론으로 들어가서
아래 코드을 보면
import javax.annotation.PostConstruct;
@Component
@Slf4j
public class SpotifyConfig {
@Value("${spotify.registration.client-id}")
private String CLIENT_ID;
@Value("${spotify.registration.client-secret}")
private String CLIENT_SECRET;
private SpotifyApi spotifyApi;
private static String accessToken;
private static Instant accessTokenExpiration;
private SpotifyApi spotifyApi
= new SpotifyApi.Builder().setClientId(CLIENT\_ID).setClientSecret(CLIENT\_SECRET).build();
//추가 메서드 생략
}
나는 문제없이 동작할거라 예상했었지만 NPE가 발생하는것이였다.
생각해보니
@Value의 필드 주입시점을 잘 모르고 있었다.
@Value 어노테이션이 필드에 붙었다면
의존관계 주입 시점에 동작한다.
추측하건데 @Component 가 붙은 SpotifyConfig 의 생성자 호출 시점은 스프링의 빈이 생성되는 시점일것이다.
참고로 스프링 빈의 생명주기는
- 스프링 컨테이너 생성
- 스프링 빈의 생성 -> 스프링의 모든 빈의 필드값 초기화(생성자 이용)
- 의존관계 주입-> 이때 @Value 값 주입
- 초기화 콜백
- 사용
- 소멸전 콜백
- 스프링 종료
즉, 2번의 빈의 생성시점에 SpotifyConfig 생성자가 호출되고 key 값은 null 이며,
2번이 지나간 후에야 key 값에 환경변수가 초기화된다.
그러므로 SpotifyConfig(빈객체)의 필드인
private SpotifyApi spotifyApi
= new SpotifyApi.Builder().setClientId(CLIENT\_ID).setClientSecret(CLIENT\_SECRET).build();
는 null 이며 NPE 가 발생하는것이다.
Solution
자 빈의 생명주기로 인해 null 이 들어간다는걸 알았다.
해결법은 바로 코드로 알아보자
해결방법 1.
@Component
@Slf4j
public class SpotifyConfig {
private String CLIENT_ID;
private String CLIENT_SECRET;
private SpotifyApi spotifyApi;
private static String accessToken;
private static Instant accessTokenExpiration;
public SpotifyConfig( @Value("${spotify.registration.client-id}") String id
, @Value("${spotify.registration.client-secret}") String password)
{
this.CLIENT_ID=id;
this.CLIENT_SECRET=password;
this.spotifyApi
= new SpotifyApi.Builder().setClientId(CLIENT\_ID).setClientSecret(CLIENT\_SECRET).build();
}
}
위의 코드로 리팩토링을 하였다.
자 그럼 위의 코드를 보면 생성자 매개변수에 @Value 어노테이션을 넣었다.
결론적으로
- Spring 이 빈 객체를 생성할 때 먼저 객체 생성에 사용할 생성자를 찾는다.
- 인수가 필요한 생성자가 발견되면 Spring은 컨테이너에서 해당 인수를 찾아 객체를 생성하기전에 주입한다.
- 생성자 매개변수 @Value 어노테이션이 달린 경우 Spring은 객체를 생성하기 전에 속성 파일에 지정된 값을 주입한다.
해서 빈생성시에 @Value 어노테이션의 값을 환경변수 값으로 초기화 할수있다.
정리하자면
@Value 필드 주입의 경우는 스프링 빈이 생성자로 빈 생성 후(2.과정) 의존관계 주입시(3.과정)에 필드값이 초기화가 되므로 스프링의 빈 생명주기로 인하여 null 값이 필연적으로 들어가게 된다.
스프링의 빈 생성자에서 환경변수의 값을 초기화 하려면 생성자 매개변수나 @PostConstruct 애너테이션을 사용하거나 애초에 생성자에 @Value 어노테이션을 붙이자.
End
이슈 해결 PR