Backend

객체 생성 시 유의해야 하는 Spring의 의존성 주입 시점

jbilee 2025. 4. 21. 00:49

Spring 애플리케이션 구현에 JdbcTemplate을 사용하는 도중 Spring의 의존성 주입이 제대로 이루어지지 않는 듯한 문제가 발생했다. 클래스의 필드에 @Autowired 애노테이션을 추가해 Spring이 주입해주기를 기대했지만, 해당 필드의 값이 null로 초기화된 것이다.

 

문제되는 코드:

// DAO 클래스
public class QueryingDAO {

    // Autowired를 사용했지만 JdbcTemplate 객체가 아닌 null로 초기화됨
    @Autowired
    private JdbcTemplate jdbcTemplate;
}

// 컨트롤러 클래스
@RestController
public class ReservationController {

    @GetMapping
    public ResponseEntity<List<Reservation>> readAll() {
      // JdbcTemplate 값이 null이어서 NPE 발생
      QueryingDAO queryingDAO = new QueryingDAO();
    }
}

 

SO 답변에 의하면 new로 QueryingDAO를 인스턴스화한 것이 원인이다. Spring이 DAO 객체를 bean으로 생성하지 않아서라는데, 그렇다고 하기에 실험삼아 작성한 아래 코드는 아무런 문제 없이 동작한다.

// 컨트롤러 클래스
@RestController
public class ReservationController {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @GetMapping
    public ResponseEntity<List<Reservation>> readAll() {
      // 메서드 내에서 new 키워드로 생성하지만 JdbcTemplate이 null로 초기화되지 않음
      QueryingDAO queryingDAO = new QueryingDAO(jdbcTemplate);
    }
}

// DAO 클래스
public class QueryingDAO {

    private final JdbcTemplate jdbcTemplate;

    public QueryingDAO(JdbcTemplate jdbcTemplate) {
      this.jdbcTemplate = jdbcTemplate;
    }
}

 

여러 방식으로 의존성을 주입해보다가 알아낸 사실은, 단순히 new 키워드가 문제인 것이 아니라 객체 인스턴스의 생성 시점에 따른다는 것이다.

 

처음 애플리케이션을 실행시킬 때 Spring은 @Autowired가 달린 필드에 객체를 주입한다. 문제가 있던 코드는 QueryingDAO를 컨트롤러의 readAll 메서드가 호출되었을 때(=런타임 때) 생성하는데, 그땐 Spring이 이미 의존성 주입을 전부 끝낸 시점인 것이고, 따라서 초반에 jdbcTemplate 필드에 객체가 주입되지 않아 null인 것이다.

 

반면에 두 번째 코드에서는 애플리케이션 가동 시점에 Spring이 컨트롤러 필드에 JdbcTemplate 객체를 주입시켜줬기 때문에, readAll 메서드가 호출된 시점에 QueryDAO를 인스턴스화해도 null이 아닌 실제 JdbcTemplate 객체를 전달할 수 있다.

 

 

Spring이 우리를 대신해 객체를 생성하고 의존성을 주입해주는 것을 제어의 역전(inversion of control)이라고 한다. 내가 직접 객체를 생성할 수도 있지만, Spring의 DI 기능과 병행해서 인스턴스화한다면 오차가 발생하지 않도록 신경써야 한다. Spring 애플리케이션을 개발할 땐 Spring이 전적으로 의존성 주입을 담당하도록 맡기는게 혼란을 줄일 수 있는 방법일 것 같다.