[Spring] 스프링 기본1 - IoC,DI, 스프링 컨테이너와 스프링 빈

easyone·2026년 9월 3일

제어의 역전 IoC(Inversion of Control)

  • 제어의 역전이란 외부에서 프로그램 제어 흐름을 관리하는 것이다.
  • AppConfig가 프로그램의 제어권을 가져간다.
  • 즉 서비스의 구현 객체는 자신의 로직을 실행하는 역할만 담당하고, 제어 흐름은 AppConfig가 가지고 있다. 구현 객체는 필요한 인터페이스를 호출하지만, 외부에서 관리하므로 어떤 구현 객체들이 실행될지 모른다.

프레임워크와 라이브러리

  • 프레임워크가 내가 작성한 코드를 제어하고, 대신 실행하면 프레임워크이다. Junit, SpringBoot 등등
  • 작성한 코드가 직접 제어 흐름을 담당하는 것은 라이브러리이다.

의존관계 주입 DI(Dependency Injection)

  • 정적 클래스 의존관계와 실행 시점에 결정되는 동적 객체(인스턴스) 의존 관계 둘을 분리해서 생각해야 한다.
  • Service 구현체는 인터페이스에 의존하고, 어떤 구현 객체가 사용될지는 모른다.

정적 클래스 의존관계

  • 클래스가 사용하는 import 문만 보고 의존관계를 쉽게 판단할 수 있다. Service가 MemberRepository를 참조하고 있는 걸 보면 의존한다는 것을 알 수 있다.
  • 그러나 실제로 어떤 객체가 서비스 구현체에 주입될지는 알 수 없다.

동적 객체 인스턴스 의존관계

  • 애플리케이션 실행 시점에 실제 생성된 객체 인스턴스의 참조가 연결된 의존 관계이다.
  • 애플리케이션 실행 시점, 런타임에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서, 클라이언트와 서버 실제 의존 관계가 연결되는 것을 의존관계 주입이라고 한다.
  • 즉 객체 인스턴스를 생성하면 참조값을 전달해서 연결되는 것이다.
  • 의존관계 주입을 사용하면 클라이언트 코드를 변경하지 않아도 호출하는 대상의 타입 인스턴스 변경이 가능하다. 즉 정적 클래스 구조를 손대지 않아도, 쉽게 변경 가능하다.

IoC 컨테이너, DI 컨테이너

  • AppConfig처럼 외부에서 객체를 생성하고 관리하면서 의존관계를 연결해주는 것을 DI 컨테이너 또는 IoC 컨테이너라고 한다.

AppConfig 설정하기

  • Appconfig 설정을 구성한다는 뜻의 @Configuration 어노테이션을 붙여주고, 각 메서드에 @Bean 어노테이션을 붙여주면, 스프링 컨테이너에 빈으로 등록한다는 것이 된다.

DL (Dependency Lookup)

  • MemberServiceImpl이 자기 자신 안에서 getBean()을 호출해서 의존 객체를 능동적으로 검색한다.

  • 문제: 이 클래스는 이제 '스프링이 있다'는 걸 알아야 하고, ApplicationContext라는 스프링 API에 직접 의존하게 된다.. → 순수한 비즈니스 로직이 프레임워크에 오염된다. 옛날 방식이다.

단순히 호출하는게 아니라, 의존관계를 검색을 하고, 자신이 필요로 하믄 의존 객체를 능동적으로 찾는다.
즉 의존관계를 맺을 객체 결정,생성은 외부 컨테이너에서 IoC로 처리하지만, 가져올 때는 스스로 컨테이너에게 요청을 한다. 이렇게 검색을 할 때 getBean() 메서드를 호출한다.

예전과 다르게 DI는, MemberServiceImpl은 getBean이 뭔지도 모르고, ApplicationContext라는게 뭔지 모른다.
그러면 getBean을 누가 호출해주냐면, @Autowired 필드나 서블릿 진입점 등이 이 역할을 대신해준다.

스프링 컨테이너

  • ApplicationContext를 스프링 컨테이너라고 한다. 모든 빈을 관리한다.
  • AppConfig 대신 스프링 컨테이너를 통해서 사용한다.
  • @Configuration이 붙은 AppConfig를 설정 정보로 사용하고, Bean 어노테이션이 붙은 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록한다. 이 객체를 스프링 빈이라고 한다.
  • 스프링 빈은 applicationContext.getBean() 메서드로 찾을 수 있고, 스프링 컨테이너에 등록된 스프링 빈을 찾아서 사용이 가능하다.

@Configuration
public class AppConfig {
	@Bean
	public MemberService memberService() {
		return new MemberServiceImpl(memberRepository());
	}
}

-> 메서드명 memberService로 빈 등록이 된 것이다.


ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = ac.getBean("memberService", MemberService.class);

이렇게하면 appConfig -> memberService 순으로 등록이 된다.

Configuration은 싱글톤을 어떻게 보장하는지?

memberService() 안에서 memberRepository()를 직접 호출할하고 있는데, 객체가 호출 시마다 생성하는 게 아니라, 이미 등록된 빈이 있으면 반환하고 없으면 등록한다. 이게 어떻게 되는 걸까..?

@Configuration이 붙은 클래스는 스프링이 CGLIB 바이트코드 조작 라이브러리로 AppConfig를 상속받은 임의의 다른 클래스를 만들고, 그 다른 클래스를 스프링 빈으로 등록한다.

이 프록시 클래스가 @Bean이 붙은 메서드마다 이미 스프링 컨테이너에 등록된 빈이 있으면 그 빈을 반환하고, 없으면 생성해서 등록 후에 반환하는 코드를 생성해준다.

그래서 싱글톤이 깨지지 않는 것이다.

@Configuration 없이 단독으로 사용한다면 @Component클래스에 Bean을 붙이는 식이 될 건데, 이렇게 하면 CGLB 프록시가 적용되지 않아서 메서드 호출 시마다 새로운 객체가 생성되고, 이 경우에 싱글톤이 깨진다.

CGLB 프록시는 중간에서 메서드 호출을 가로채는 프록시다. 그래서 적용되면 이미 있는 것을 반환하고 호출은 패스하도록 해주는 것이다.

근데 이 방법이 컴포넌트 스캔이랑은 어떻게 다른지? 궁금했다..

컴포넌트 스캔 + 생성자 주입 방식과의 차이

여기서 MemberService가 memberRepository를 생성자 파라미터로 가지고 있기 때문에 repository 메서드를 호출하는게 아니라, 컨테이너가 이미 만들어놓은 인스턴스를 생성자 파라미터로 꽂아준다.

즉 여기서는 프록시가 별도로 필요가 없다.

@Repository
public class MemoryMemberRepository implements MemberRepository { ... }

@Service
public class MemberServiceImpl implements MemberService {
    private final MemberRepository memberRepository;

    public MemberServiceImpl(MemberRepository memberRepository) { // 호출X , 주입
        this.memberRepository = memberRepository;
    }
}

@Service
public class OrderServiceImpl implements OrderService {
    private final MemberRepository memberRepository;

    public OrderServiceImpl(MemberRepository memberRepository) { // 주입
        this.memberRepository = memberRepository;
    }
}

Bean

빈 또는 빈 오브젝트는 스프링이 IoC 방식으로 관리하는 오브젝트라는 뜻이다.
스프링을 사용하는 애플레이케이션에서 만들어지는 모든 오브젝트가 다 빈은 아니고, 스프링이 생성과 제어를 담당하는 오브젝트만을 빈이라고 한다.

BeanFactory

  • 스프링 컨텍스트의 최상위 인터페이스이다.
  • 스프링의 IoC를 담당하는 핵심 컨테이너를 가리킨다. 빈의 생명 주기, 부가적인 빈 관리 기능을 담당한다.
  • 보통 빈 팩토리를 바로 사용하는 건 아니고, 이를 확장한 애플리케이션 컨텍스트를 사용한다. BeanFactory 안에는 getBean()과 같은 메서드가 정의되어 있다.

ApplicationContext

  • ApplicationContext
    • 앞서 스프링 컨테이너라고 했는데, 인터페이스이다.
    • 빈 팩토리를 확장한 IoC 컨테이너라고 할 수 있다. 빈 팩토리의 기능과 동일한데 여기서 스프링 제공 부가 서비스를 추가로 제공한다.
    • 즉 빈 팩토리를 상속한다. 빈 팩토리는 사용하지 않지만, 구분해서 설명하기는 한다.
  • 모든 컨테이너는 XML을 기반으로 만들 수 있고, 애노테이션 기반의 자바 설정 클래스로 만들 수 있다.
  • 직전에 AppConfig를 사용한 방식이 어노테이션 기반의 자바 클래스로 스프링 컨테이너를 만든 것이다.

스프링 컨테이너 생성 과정

빈 등록

  • 스프링 컨테이너를 생성할 때는, Appconfig.class라는 것에 구성 정보를 지정하고 만들어줘야 한다.
  • 스프링 컨테이너는 파라미터로 들어오는 설정 클래스 정보를 사용해서 스프링 빈을 등록한다.

@Bean 어노테이션을 달아준 memberService가 있다고 하자. 이걸 스프링 컨테이너의 빈 저장소에 이름,객체 이렇게 등록을 해준다. 빈 이름은 메서드명을 하용하지만, 직접 부여할 수도 있다.

빈은 항상 다른 이름을 부여해야 한다. 같은 이름을 부여하면 다른 빈이 무시되거나 덮어쓰기 하거나 하는 설정에 따라 오류가 발생한다.

스프링 빈 의존관계 설정

memberService -> memberRepository
orderService -> discountPolicy

이런식으로 의존관계가 있을 것이다. 스프링 컨테이너는 설정 정보를 참고해서 의존관계를 주입한다.
스프링 컨테이너는 설정 정보를 참고해서 의존관계를 주입(DI)한다.

빈 조회

빈 조회하기: ac.getBeanDefinitionNames() : 스프링에 등록된 모든 빈 이름을 조회한다.
빈 이름으로 빈 객체 조회 : ac.getBean(), ac.getBean(빈이름,타입)
동일한 타입이 둘 이상이면 오류가 발생한다.

ac.getBeansOfType()을 사용하면 해당 타입의 모든 빈을 조회할 수 있다.

스프링 빈 조회 - 상속 관계

  • 부모 타입으로 조회하면 자식 타입도 함께 조회한다.
  • 모든 자바 객체 부모인 Object 타입으로 조회하면 모든 스프링 빈을 조회한다.

ApplicationContext가 제공하는 부가 기능

  • 메시지소스를 활용한 국제화 기능: 한국 -> 한국어 영어 -> 영어로 출력
  • 환경변수: 로컬, 개발, 운영 등의 프로파일을 구분해서 처리한다.
  • Application Event: 이벤트 발행,구독 모델을 편리하게 지원한다.
  • 편리한 리소스 조회: 파일, 클래스패스,외부 등에서 리소스를 편리하게 조회한다.

다양한 설정 형식 지원 - 자바 코드, XML

  • 애노테이션 기반 자바 코드 설정 사용
  • XML 설정 사용: 이건 잘 하지 않는데, 컴파일 없이 빈 설정 정보를 변경할 수 있다는 장점이 있다.
  • GenericXmlApplicationContext를 사용하면서 xml 설정 파일을 넘기면 된다.
public class XmlAppContext {
	@Test
	void xmlAppContext() {
		ApplicationContext ac = new
	GenericXmlApplicationContext("appConfig.xml");
		MemberService memberService = ac.getBean("memberService",
	MemberService.class);
		assertThat(memberService).isInstanceOf(MemberService.class);
}

BeanDefinition - 스프링 빈 설정 메타 정보

  • BeanDefinition 이라는 추상화로 스프링은 다양한 설정 형식을 지원한다. 즉 BeanDefinition 자체가 인터페이스고 스프링 컨테이너는 추상화된 BeanDefinition에만 의존한다.
  • 스프링 컨테이너는 자바 코드인지, XML인지 몰라도 된다. 오직 BeanDefinition만 알면 된다.
    BeanDefinition 을 빈 설정 메타정보라 한다.
    @Bean , <bean> 당 각각 하나씩 메타 정보가 생성된다.
    스프링 컨테이너는 이 메타정보를 기반으로 스프링 빈을 생성한다.

  • AnnotationConfig안의 AnnotatedBeanDefinitionReader : AnnotationConfigApplicationContext 안에 들어가보면 있는 건데, 이걸 사용해서 AppConfig.class 내부의 설정 정보 등등이 있는 코드를 읽고 BeanDefinition이라는 빈 메타정보를 생성한다.
  • Xml도 마찬가지로 가능하다. AppConfig를 가지고 BeanDefinition을 생성한다.
profile
백엔드 개발자 지망 대학생

1개의 댓글

comment-user-thumbnail
2026년 9월 3일

못하는 게 뭘까?

답글 달기