카테고리 없음

@Value애너테이션과 스프링 빈 생명주기 오류(2024-02-05)

최동훈1 2024. 2. 5. 19:34

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 의 생성자 호출 시점은 스프링의 빈이 생성되는 시점일것이다.

참고로 스프링 빈의 생명주기는

  1. 스프링 컨테이너 생성
  2. 스프링 빈의 생성 -> 스프링의 모든 빈의 필드값 초기화(생성자 이용)
  3. 의존관계 주입-> 이때 @Value 값 주입
  4. 초기화 콜백
  5. 사용
  6. 소멸전 콜백
  7. 스프링 종료

즉, 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 어노테이션을 넣었다.

결론적으로

  1. Spring 이 빈 객체를 생성할 때 먼저 객체 생성에 사용할 생성자를 찾는다.
  2. 인수가 필요한 생성자가 발견되면 Spring은 컨테이너에서 해당 인수를 찾아 객체를 생성하기전에 주입한다.
  3. 생성자 매개변수 @Value 어노테이션이 달린 경우 Spring은 객체를 생성하기 전에 속성 파일에 지정된 값을 주입한다.

해서 빈생성시에 @Value 어노테이션의 값을 환경변수 값으로 초기화 할수있다.

정리하자면

@Value 필드 주입의 경우는 스프링 빈이 생성자로 빈 생성 후(2.과정) 의존관계 주입시(3.과정)에 필드값이 초기화가 되므로 스프링의 빈 생명주기로 인하여 null 값이 필연적으로 들어가게 된다.

스프링의 빈 생성자에서 환경변수의 값을 초기화 하려면 생성자 매개변수나 @PostConstruct 애너테이션을 사용하거나 애초에 생성자에 @Value 어노테이션을 붙이자.


End

 

이슈 해결 PR

: ♻️ [refactor] : spotify API 성능 개선 by ulsandonghun · Pull Request #66 · Playlist-pack/Server (github.com)