본문 바로가기

Back-End/Spring

Spring은 어떻게 객체를 관리할까?

✅ Spring에서 @Component와 의존성 주입이 동작하는 원리

  • "Spring에서 객체 생성과 관리, 그리고 의존성 주입은 어떻게 이루어질까?"
  • "왜 @Component를 붙이면 자동으로 빈이 등록될까?"
  • "Spring AOP 프록시는 언제 적용될까?"

Spring을 사용하다 보면 @Component, @Service, @Repository 등의 어노테이션을 붙이면 자동으로 빈(Bean)으로 등록되고 주입되는 것을 볼 수 있습니다.

먼저 들어가기 앞서

Spring 컨테이너(ApplicationContext)란?

Spring에서 객체의 생성과 생명주기를 관리를 담당하는 핵심 요소가 Spring 컨테이너입니다.
이 컨테이너는 빈(Bean)을 관리하고, 필요할 때 의존성을 주입하는 역할을 합니다.

✔️ Spring에서 컨테이너는 크게 두 가지로 구분할 수 있습니다.

컨테이너 설명
IoC컨테이너 객체(Bean)의 생성과 의존성 주입을 담당
ApplicationContext IoC 컨테이너의 확장 개념, 빈 관리 뿐만 아니라 AOP, 이벤트 리스너, 메시지 소스 처리 등을 포함

✔️ 즉 ApplicationContext는 IoC 컨테이너의 기능을 포함하면서 더 많은 기능을 제공합니다.
SpringBoot는 일반적으로 ApplicationContext를 사용하여 빈을 관리합니다.


✅ 1. Spring에서 @Component는 어떻게 동작할까?

Spring이 @Component를 인식하는 과정
Spring에서는 @Component를 사용하여 특정 클래스를 빈으로 등록할 수 있습니다.

  1. Spring 애플리케이션이 실행되면서 ApplicationContext가 생성됩니다.
  2. @ComponentScan을 수행하면서 해당 패키지 및 하위 패키지에서 @Component가 붙은 클래스를 검색합니다.
  3. @Component가 붙은 클래스를 Spring 컨테이너에서 관리하는 빈(Bean)으로 등록합니다.
  4. 이후, @Autowired 또는 생성자 주입을 통해 자동으로 의존성을 주입할 수 있습니다.

✅ 2. @Component 예제 코드로 이해하기

1️⃣ @Component를 사용하여 빈 등록

import org.springframework.stereotype.Component;

@Component  // Spring이 자동으로 스캔하여 빈으로 등록
public class MyComponent {
    public void doSomething() {
        System.out.println("MyComponent 동작 중...");
    }
}

@Component를 사용하면 Spring이 자동으로 이 클래스를 스캔하고 빈으로 등록합니다.

2️⃣ @Autowired를 사용하여 빈 주입

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service  // Service도 내부적으로 @Component 역할을 함
public class MyService {

    private final MyComponent myComponent;

                // @RequiredArgsConstructor 사용하면 생성자 만들 필요 없음
    @Autowired  // Spring이 MyComponent 빈을 찾아 자동 주입
    public MyService(MyComponent myComponent) {
        this.myComponent = myComponent;
    }

    public void execute() {
        myComponent.doSomething();  // 자동 주입된 빈 사용
    }
}

@Service@Component와 동일한 역할을 수행하며, Spring이 자동으로 MyComponent 빈을 찾아 MyService에 주입합니다.
📌 참고) @RequiredArgsConstructor를 클래스에 달아주면 생성자를 따로 만들 필요가 없습니다!

3️⃣ 빈이 등록되었는지 확인

import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

@Component
public class BeanChecker {

    private final ApplicationContext applicationContext;

    public BeanChecker(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    public void checkBean() {
        boolean isBeanPresent = applicationContext.containsBean("myComponent");
        System.out.println("MyComponent 빈 등록 여부: " + isBeanPresent);
    }
}

MyComponent 빈 등록 여부: true

applicationContext.containsBean("myComponent")를 사용하면 Spring 컨테이너에 빈이 등록되었는지 확인할 수 있습니다.

4️⃣ 실행하면 자동으로 빈이 등록되고 주입됨
MyService에서 MyComponentdoSomething() 메서드를 호출하면, 자동 주입된 빈을 통해 실행됩니다.


✅ 3. Spring의 의존성 주입(DI)은 누가 해주는가?

Spring에서 의존성을 주입하는 역할을 하는 것은 바로 Spring IOC 컨테이너입니다.

의존성 주입(DI)의 과정

1️⃣ Spring이 애플리케이션을 시작하면, IOC 컨테이너가 생성됨

  • @ComponentScan을 수행하여 @Component, @Service, @Repository, @Controller 등의 어노테이션이 붙은 클래스를 자동으로 스캔
  • 스캔된 클래스들은 Spring 컨테이너에서 관리되는 빈(Bean)으로 등록됨

2️⃣ 필요한 의존성을 자동으로 주입

  • @Autowired, 생성자 주입을 사용하면 Spring 컨테이너가 적절한 빈을 찾아 주입
  •  기본적으로 Spring은 모든 빈을 싱글톤(Singleton)으로 관리하여, 한 번 생성된 빈을 재사용함

✅ 4. Spring 컨테이너와 AOP 프록시의 흐름

AOP 프록시 적용 과정

Spring은 AOP를 적용하기 위해 프록시 객체를 생성합니다.
이 프록시는 트랜잭션 관리(@Transactional), 재시도 로직(@Retryable), 로깅 등 다양한 부가 기능을 제공할 수 있습니다.


Spring 컨테이너(AOP 프록시 포함)의 동작 흐름

1️⃣ Spring 컨테이너(ApplicationContext) 생성

  • @ComponentScan을 수행하여 @Component, @Service, @Repository, @Controller가 붙은 클래스를 스캔
  • 스캔된 클래스는 Spring 컨테이너에서 관리하는 빈(Bean)으로 등록

2️⃣ AOP 프록시 적용 여부 확인

  • @Transactional, @Retryable과 같은 AOP 어노테이션이 붙어 있으면 Spring이 해당 빈을 AOP 프록시 객체로 감쌈
  • 프록시는 메서드 호출을 가로채서 트랜잭션 관리나 재시도 로직을 적용

3️⃣ 빈을 다른 클래스에서 주입받아 사용

  • @Autowired 또는 생성자 주입을 통해 자동으로 의존성을 주입
  • 프록시 객체가 주입되면, 실제 메서드 실행 전에 AOP 기능이 동작

❌ 하지만, 같은 클래스 내부에서 메서드를 직접 호출(Self-Invocation) 하면 프록시를 거치지 않기 때문에 AOP 기능이 적용되지 않습니다.


Self-Invocation 문제와 해결 방법

❌ 잘못된 예제 : 자기 자신을 직접 호출(self-Invocation)

@Service
public class MyService {

    @Transactional
    public void outerMethod() {
        System.out.println("외부 메서드 실행");
        innerMethod();  // 같은 클래스 내부에서 직접 호출 → 프록시 미적용
    }

    @Transactional
    public void innerMethod() {
        System.out.println("내부 메서드 실행 (트랜잭션 적용 안됨)");
    }
}

문제: innerMethod()가 같은 클래스 내부에서 직접 호출되므로, 프록시를 거치지 않고 실행되어 @Transactional이 적용되지 않습니다.

✅ 해결 방법 : 프록시 객체를 통해 호출

@Service
public class MyService {

    private final MyService self;

    public MyService(@Lazy MyService self) {  // ApplicationContext를 통해 주입받아 프록시 적용
        this.self = self;
    }

    public void outerMethod() {
        System.out.println("외부 메서드 실행");
        self.innerMethod();  // 프록시 객체를 통한 호출 → @Transactional 적용됨
    }

    @Transactional
    public void innerMethod() {
        System.out.println("내부 메서드 실행 (트랜잭션 적용)");
    }
}

해결: self.innerMethod()ApplicationContext에서 주입받은 프록시 객체를 통해 실행되므로, @Transactional이 정상 동작합니다.


✅ 결론

“Spring 컨테이너(ApplicationContext)는 빈을 생성하고 관리하며, 필요할 때 자동으로 주입하는 역할을 한다.”

“AOP 프록시는 Spring 컨테이너가 관리하는 빈을 감싸서 트랜잭션이나 재시도 기능을 적용할 수 있도록 한다.”