티스토리 뷰

간단한 Voucher 관리 프로그램을 만들던 도중,
@Value로 주입받은 값을 생성자에서 사용하려니, NPE가 발생하여
한참의 삽질 끝에 정확하게 언제 주입이 되는지 알아보게 되었습니다🚀

 

상황

바우처 정보를 파일을 통해 관리하기 위해, yaml 파일에 파일 위치를 명시한 후 

레포지토리 클래스에서 파일에 접근하여 바우처 정보를 읽고 쓰고자 아래와 같이 코드를 작성하였습니다.

 

문제의 코드🤔

@Repository
@Profile("default")
public class FileVoucherRepository implements VoucherRepository {
    private Map<UUID, Voucher> vouchers;
    private final File voucherInfoCsv;
    @Value("${spring.file.voucher.path}")
    private String vouchInfoCsvPath;
    private final static Logger logger = LoggerFactory.getLogger(FileVoucherRepository.class);

    public FileVoucherRepository() {
        voucherInfoCsv = new File(vouchInfoCsvPath);
        prepareCsv();
        vouchers = getAllVouchersFromCSV();
    }
    
    // ...

 

인텔리제이에서 이렇게 값이 표시돼서 되는 줄 알았는데

 

 

실행시켜보니, 아래와 같이 vouchInfoCsvPath에 null이 들어가고 있었습니다..🤨

 

오류 발생 화면

 

디버깅 결과👾

 

디버깅

 


왜 NPE가발생했을까❓

Spring Framework에서 '@Value'로 주입받은 값은 빈 생성자보다 먼저 주입되지 않는다고 합니다. 이는 스프링의 빈 라이프사이클과 빈 프로퍼티 주입 시기와 관련이 있습니다.

 

빈 생성의 경우,

@Repository 어노테이션을 사용하여 해당 클래스를 Spring의 빈으로 등록했을 때, 해당 빈의 생성자가 호출됩니다.

하지만 이 시점에서는 아직 '@Value'로 주입받은 프로퍼티가 설정되기 전이므로 해당 프로퍼티는 초기화되지 않는 null 값을 가지게 됩니다.

 

➡️다시 한번 정리하면, 생성자가 호출이 @Value로 프로퍼티 주입보다 먼저 이루어져 생성자 호출에서 해당 값에 null이 들어가 NPE가 발생하게 된 것입니다.

 


Spring Bean Life Cycle🚴‍♀️


간단히 하나씩 살펴보면🔍

1. Instantiation 인스턴스화

빈 객체가 메모리에 생성됩니다. 객체가 생성되고 빈의 클래스의 생성자가 호출됩니다.

 

2. Populating Properties 프로퍼티 설정

빈의 프로퍼티, 의존성 주입 등이 설정됩니다. @Autowired, @Value, XML 구성 등을 통해 프로퍼티가 주입됩니다.

 

3. Initialization 초기화

InitializingBean 인터페이스의 afterPropertiesSet() 메서드 또는 @PostConstruct 어노테이션이 적용된 메서드가 호출되어 빈의 초기화 작업을 수행합니다. 이 초기화 메서드를 통해 빈을 사용할 준비를 마칩니다.

 

4. Destroy 소멸

빈이 더 이상 필요하지 않을 때, 빈의 소멸 작업을 수행합니다. 이 단계에서 DisposableBean 인터페이스의 destroy() 메서드나 @PreDestory 어노테이션이 적용된 메서드가 호출됩니다. 이 단계는 빈의 수명 주기가 끝날 때 발생하며, 메모리 리소스를 해제하거나 다른 정리 작업을 수행하는 데 사용됩니다.

 


그럼 어떻게 사용할 수 있을까❓

@Value로 주입된 프로퍼티는 빈 생성자가 호출된 후에 설정되므로

빈 초기화 단계에서 호출되는 @PostConstruct 어노테이션이 적용된 메서드를 활용하면 @Value로 주입된 프로퍼티를 안전하게 사용할 수 있습니다.