본문 바로가기
Spring

Spring Core 꼬리 질문 해보기

by CodingMasterLSW 2025. 4. 28.

시작 질문 : Spring Bean이 뭐에요??

-> Spring Bean은 Spring Container에서 관리되는 객체를 의미합니다.  Bean은 내부적으로 BeanDefinition을 가지고 있고, 그 안에 Bean에 대한 정보(class, scope...)를 가지고 있습니다. 

 

그러면 Spring Container에 Bean을 어떻게 등록할 수 있나요?

 

-> 여러 방법이 있지만, 가장 보편적으로 사용하는 방법은 Annotation을 사용한 방법입니다. 

Bean으로 등록하고 싶은 클래스 상단에 @Component 어노테이션을 붙이면, 추후 @ComponentScan을 통해 @Component 어노테이션이 붙어있는 모든 클래스를 컨테이너에 빈으로 등록할 수 있습니다. XML 파일을 통해 주입하는 방법도 있다고 알고 있지만, 자세히는 모르겠습니다.

 

그러면 스프링은 어떻게 XML, Annotation과 같은 여러 방법으로 Bean을 등록할 수 있게 지원해 주는 걸까요?

-> Spring Container는 BeanDefinition이라는 인터페이스에 의존하고 있습니다. 해당 인터페이스에서 Bean 등록 메서드를 지원하고, 이후 하위 구현체에서 XML / Annotation의 방법을 구현해 놓은 것으로 알고 있습니다. 인터페이스에 의존하니, 하위 구현체를 몰라도 되어 유연한 설계라고 생각합니다.

 

위에서 Spring Container라는 말이 계속 나오는데, Spring Container가 뭐죠??

-> Spring Container를 다른 말로 말하자면, IoC Container라고도 할 수 있습니다. 

IoC는 Inversion of Control, 제어의 역전이라고 해석할 수 있습니다. 여기서 말하는 제어의 역전은 의존성을 말합니다.

 

즉, Spring Container는 의존성을 관리하는 컨테이너라고 말할 수 있습니다.

기존의 프로그래밍 환경에서는 개발자가 직접 의존성을 관리했습니다. 하지만 IoC Container를 통해 의존성을 관리함으로써 편안하게 개발을 할 수 있습니다.

 

편안하게 개발할 수 있다는 게 어떤 건가요? IoC 컨테이너를 사용하면 얻을 수 있는 이점에 대해 조금 더 자세하게 말해주세요.

-> 개발자가 의존성을 관리하지 않아도 되는 게 가장 큰 이점이라고 생각합니다. 예를 들어 A라는 객체를 생성할 때, B, C와 의존성을 가지고 있다고 하겠습니다. 그리고 B는 D, E, F / C는 G, H, I와 의존성을 가지고 있고, D, E, F, G, H, I 가 각각 3개의 의존성을 추가로 가지고 있다고 가정해 보겠습니다.

 

그렇다면 A라는 객체를 생성할 때 다음과 같은 과정을 거쳐야 할 것입니다.

A a = new A(
new B(new D(), new E(), new F()), 
new C(new G(), new H(), new I()))
...

 

A 객체를 생성하는 데 코드가 너무 길어지고, 복잡합니다. 이를 개발자가 관리하기에는 시간도 오래 걸리고, 실수할 가능성이 있다고 생각합니다. IoC 컨테이너를 사용한다면, 위와 같은 과정을 생략하고 의존성 주입(DI)을 통해 IoC 컨테이너에서 의존성을 관리할 수 있어 훨씬 수월하게 코드를 작성할 수 있다고 생각합니다.

 

의존성 주입이 뭔가요?? 

-> DI(Dependency Injection). 객체를 내부에서 생성하지 않고 외부에서 생성 후 주입받는 방식입니다.

@Service
public class ReservationService {

    private final JdbcReservationDaoImpl reservationDao = new JdbcReservationDaoImpl();
    private final JdbcReservationTimeDaoImpl jdbcReservationTimeDao = new JdbcReservationTimeDaoImpl();
    }

 

위의 코드는 의존성 주입을 하고 있지 않습니다. 이 경우 인터페이스가 아닌, 구체클래스와 강하게 의존성을 가지고 있죠. 

 

@Service
public class ReservationService {

    private final ReservationDao reservationDao;
    private final ReservationTimeDao reservationTimeDao;

    public ReservationService(ReservationDao reservationDao, ReservationTimeDao reservationTimeDao) {
        this.reservationDao = reservationDao;
        this.reservationTimeDao = reservationTimeDao;
    }

 

ReservationDao, ReservationTimeDao라는 인터페이스를 만들고 외부에서 해당 객체를 주입해 준다면, 구체클래스가 아닌 인터페이스에 의존합니다. 이는 DIP를 잘 지키며, OCP 또한 잘 지킬 수 있는 구조입니다.

 

Spring 환경에서 의존성 주입을 하는 방법은 어떤 방법이 있을까요?

-> 생성자 주입, 세터 주입, 필드 주입이 있습니다.

 

생성자 주입은 상단 코드에서 사용한 방법입니다. 

의존성 주입을 하기 위해서는 @Autowired 어노테이션을 사용할 수 있습니다. 생성자 주입 방식에서, 생성자가 하나일 때는 @Autowired 어노테이션을 생략해도 되지만, 생성자가 두 개 이상일 경우에는 @Autowired 어노테이션을 붙여줘야 합니다.

 

필드 주입

@Service
public class ReservationService {

    @Autowired
    private ReservationDao reservationDao;
    
    @Autowired
    private ReservationTimeDao reservationTimeDao;

 

간편해 보이지만, 외부에서 값을 변경하기 힘들다는 단점이 있습니다. 즉, 필드 주입을 사용하면 테스트 코드를 작성하기 어렵습니다.

또한, 불변을 보장할 수 없습니다.

 

 

세터 주입

@Service
public class ReservationService {

    private ReservationDao reservationDao;
    private ReservationTimeDao reservationTimeDao;

    @Autowired
    public void init(ReservationDao reservationDao, ReservationTimeDao reservationTimeDao) {
        this.reservationDao = reservationDao;
        this.reservationTimeDao = reservationTimeDao;
    }

 

세터 주입 또한 필드값들을 불변으로 선언할 수 없습니다.

 

주로 생성자 주입을 자주 사용하는데 이유는 세터, 필드주입은 필드값을 불변으로 유지할 수 없다는 단점이 존재해 생성자 주입을 주로 사용하고 있습니다.

 

IoC 컨테이너에서 Bean을 관리한다고 했는데, Bean을 관리한다는 건 무슨 말일까요?

-> 크게 두 가지 개념으로 나눌 수 있을 것 같습니다.

 

1) 빈 스코프

빈을 생성할 때, Bean Definition 내부에 빈 스코프 값이 존재합니다.

- singleton

- prototype

- request

- session

- application

 

이 중에, singleton, prototype, request에 대해 설명해 보겠습니다.

 

빈 스코프는 기본적으로 singleton으로 설정되어 있습니다. 싱글톤은 IoC 컨테이너가 애플리케이션 시작 시 빈 객체를 하나만 생성하고, 이후 요청이 올 때마다 이 하나의 객체를 공유합니다.

 

프로토타입의 경우, getBean() 요청마다 새로운 객체를 생성해 반환합니다. 생성 이후에는 IoC 컨테이너에서 객체를 관리하지 않습니다.

 

request의 경우 HTTP 요청이 시작될 때 생성되고, 요청이 완료되면 소멸합니다. 각 HTTP 요청마다 별도의 객체 인스턴스를 사용합니다.

 

2) 빈 생명 주기

빈은 다음과 같은 생명 주기를 가지고 있습니다.

 

빈 생성 -> 의존성 주입 -> 초기화 콜백 -> 사용 -> 소멸 콜백

 

초기화 콜백은 @PostConstruct를 통해 사용할 수 있고, Bean이 생성된 직후에 해야 할 작업들을 정의할 수 있습니다. 소멸 콜백은 @PreDestroy를 통해 사용할 수 있고, Bean이 소멸되기 직전에 해야 할 작업들을 정의할 수 있습니다.

 

Singleton 스코프의 경우, IoC 컨테이너 내부에서 빈 객체를 가지고 있기에 @PreDestroy 메서드를 활용할 수 있습니다. 하지만 Prototype 스코프의 경우, IoC 컨테이너 내부에서 빈 객체를 가지고 있지 않아 @PreDestroy 메서드를 활용할 수 없습니다. Prototype의 경우, 객체가 생성된 이후의 책임은 사용하는 곳에 떠넘기기 때문이죠. 그렇기에 필요한 경우 객체 사용자가 직접 소멸처리를 해야 합니다.

 

결국 IoC 컨테이너에서 Bean을 관리한다는 것은, Bean의 생명 주기를 관리하는 것이라고 생각합니다.

 

왜 IoC 컨테이너에서는 싱글톤 스코프가 기본값일까요??

-> Spring은 Web에서 사용한다고 가정하고 만들어진 것으로 알고 있습니다. 기본적으로 Web에서는 클라이언트와 여러 번의 요청이 왔다 갔다 합니다. 만약 IoC 컨테이너에서 요청을 받을 때마다 새로운 생성 한다면, 객체를 재사용하지 않고 새롭게 만들어내겠죠. 이를 통해 불필요한 메모리가 낭비되기에 싱글톤 스코프가 기본값이지 않을까 생각합니다.

 

하나의 타입에 대해 여러 개의 빈이 존재한다면 오류가 날 거예요. 이 경우에 어떻게 문제를 해결할 수 있을까요?

-> @Qualifier, @Primary와 같은 어노테이션을 사용해 우선순위를 정해주면 될 것 같습니다.

 

 

 

잘못된 부분에 대한 지적은 언제나 환영합니다.

'Spring' 카테고리의 다른 글

Spring HTTP 처리 과정  (0) 2025.06.29
Spring Response/Request 어노테이션  (0) 2025.04.16