IoC Container와 Bean

3줄 요약

  • IoC는 객체 생성, 설정, 의존성 연결의 주도권을 애플리케이션 코드가 아니라 Spring Container가 맡는 구조다.
  • Bean은 Spring Container가 생성, 설정, 조립, 관리하는 객체이며, 대부분의 Spring 기능은 Bean을 기준으로 동작한다.
  • DI는 Bean 사이의 협력 관계를 외부에서 주입해 코드가 구체적인 생성 방식보다 역할과 계약에 집중하게 만든다.

핵심 정리

  • Spring의 핵심은 ApplicationContext가 설정 메타데이터를 읽어 객체 그래프를 구성하고 관리한다는 점이다.
  • 개발자는 객체를 직접 만들기보다 Bean 정의의 입력을 제공하고, 컨테이너는 그 정의를 바탕으로 객체를 생성하고 의존성을 연결한다.
  • Bean은 단순히 @Component가 붙은 클래스가 아니라 Spring이 생명주기를 관리하는 객체다.
  • DI는 필드에 값을 넣는 기술이 아니라 객체 간 협력 관계를 명시적으로 구성하는 설계 방식이다.
  • 생성자 주입은 필수 의존성을 객체 생성 시점에 강제하고, 테스트와 불변성 측면에서 가장 안정적인 기본 선택지다.

헷갈리는 지점

  • IoC를 “객체를 대신 만들어 주는 기능” 정도로만 이해하기 쉽다. 실제로는 생성보다 제어 흐름의 주도권이 바뀐다는 점이 핵심이다.
    • 핵심은 객체가 필요한 시점과 연결 방식을 코드가 직접 결정하지 않는다는 것이다.
    • Spring Container가 객체 그래프를 먼저 구성하고, 애플리케이션 코드는 주입받은 협력 객체를 사용한다.
  • Bean을 클래스와 같은 것으로 생각하기 쉽다. 클래스에 애너테이션을 붙이면 Bean이 되기 때문이다.
    • 핵심은 Bean은 클래스가 아니라 컨테이너 안에 등록된 객체 인스턴스라는 점이다.
    • 같은 클래스라도 등록 방식, 이름, Scope, factory method에 따라 다른 Bean 정의와 다른 Bean 인스턴스가 될 수 있다.
  • DI를 쓰면 객체지향 설계를 자동으로 잘하게 된다고 오해하기 쉽다. 의존성을 주입받는다고 해서 역할 분리가 저절로 생기지는 않는다.
    • 핵심은 DI가 좋은 경계를 강제하는 도구가 아니라 좋은 경계를 드러내는 도구라는 점이다.
    • 인터페이스, 생성자 주입, 명확한 책임 분리가 함께 있어야 DI의 장점이 살아난다.

확인 질문

  • IoC에서 제어권이 이동한다는 말은 무엇을 뜻하는가?
    • 객체 생성, 설정, 의존성 연결, 생명주기 관리의 주도권이 애플리케이션 코드에서 Spring Container로 이동한다는 뜻이다.
  • Bean은 무엇인가?
    • Spring Container가 생성하고 설정하고 조립하며 생명주기를 관리하는 객체다.
  • 생성자 주입을 기본으로 삼는 이유는 무엇인가?
    • 필수 의존성을 누락할 수 없고, 객체를 불변에 가깝게 만들며, 테스트에서 의존 관계가 명확하게 드러나기 때문이다.

의존성 주입

3줄 요약

  • DI는 객체가 사용할 협력 객체를 직접 만들지 않고 외부에서 받는 방식이다.
  • Spring은 생성자, setter, 필드 주입을 지원하지만 필수 의존성에는 생성자 주입이 가장 명확하다.
  • 좋은 DI는 타입을 맞추는 것보다 의존 방향, 책임 경계, 테스트 가능성을 함께 설계하는 것이다.

핵심 정리

  • 생성자 주입은 객체 생성 시점에 필요한 모든 의존성이 준비되어야 하므로 필수 의존성이 null인 불완전한 객체를 만들기 어렵다.
  • setter 주입은 선택 의존성이나 런타임 재설정 가능성이 필요한 경우에 제한적으로 쓴다.
  • 필드 주입은 코드가 짧지만 의존성이 숨고 테스트에서 직접 생성하기 어려워진다.
  • @Autowired는 타입을 기준으로 후보 Bean을 찾고, 후보가 여러 개이면 @Primary, @Qualifier, Bean 이름, 제네릭 정보 같은 추가 선택 기준이 필요하다.
  • DI가 많아진다는 것은 클래스가 많은 책임을 가지고 있다는 신호일 수 있으므로 생성자 파라미터 수가 과도하면 Spring 설정 문제가 아니라 책임 분리 문제를 먼저 의심한다.

헷갈리는 지점

  • @Autowired가 DI의 본질이라고 생각하기 쉽다. 애너테이션이 가장 눈에 잘 보이기 때문이다.
    • 핵심은 DI의 본질은 객체가 협력 객체를 직접 생성하지 않는다는 설계 원칙이다.
    • @Autowired는 Spring이 그 원칙을 구현하는 여러 방법 중 하나다.
  • 필드 주입이 가장 간단하므로 좋은 방식이라고 느끼기 쉽다. 생성자 코드가 줄어들기 때문이다.
    • 핵심은 간결함보다 의존성의 명시성이 더 중요하다는 점이다.
    • 필드 주입은 객체를 직접 생성해 테스트하기 어렵고 필수 의존성 누락을 컴파일 단계에서 드러내기 어렵다.
  • 생성자 주입을 쓰면 순환 참조 문제가 더 자주 드러나서 불편하다고 느끼기 쉽다. setter 주입보다 빨리 실패하기 때문이다.
    • 핵심은 순환 참조가 숨겨질 문제가 아니라 설계상 끊어야 할 의존 방향이라는 점이다.
    • 생성자 주입이 순환 구조를 드러내면 책임 분리나 이벤트, 중간 정책 객체를 검토한다.
  • 인터페이스 주입만 하면 결합도가 낮아진다고 오해하기 쉽다. 인터페이스 자체가 잘못된 경계를 가질 수도 있기 때문이다.
    • 핵심은 인터페이스가 실제 변경 가능성과 역할 경계를 표현해야 한다는 점이다.
    • 구현체가 하나뿐이고 변경 가능성이 낮다면 무조건 인터페이스를 만드는 것이 답은 아니다.

확인 질문

  • DI는 객체 생성과 어떤 관계가 있는가?
    • 객체가 협력 객체를 직접 생성하지 않고 외부에서 제공받게 만들어 생성 책임과 사용 책임을 분리한다.
  • 필수 의존성에는 어떤 주입 방식이 가장 적합한가?
    • 생성자 주입이 가장 적합하다.
  • 주입 후보 Bean이 여러 개일 때는 어떻게 구분하는가?
    • @Primary, @Qualifier, Bean 이름, 제네릭 타입 정보 등을 이용해 후보를 좁힌다.

Bean 등록 방식

3줄 요약

  • Bean은 컴포넌트 스캔, Java 설정, 조건부 자동 설정 등 여러 경로로 등록될 수 있다.
  • @Component, @Service, @Repository, @Controller는 클래스를 스캔 대상 Bean으로 만드는 대표 애너테이션이다.
  • @Configuration@Bean은 외부 라이브러리 객체나 생성 과정이 복잡한 객체를 명시적으로 등록할 때 강하다.

핵심 정리

  • 컴포넌트 스캔은 정해진 base package 아래에서 stereotype 애너테이션이나 @Component 메타 애너테이션이 붙은 클래스를 찾아 Bean 정의로 등록한다.
  • @Service, @Repository, @Controller는 모두 @Component 계열이지만 계층의 의미와 도구, Aspect, 예외 변환 같은 후처리 가능성을 드러낸다.
  • @Bean은 개발자가 직접 객체 생성 메서드를 제공하고, 반환 객체를 Spring Bean으로 등록한다.
  • @Configuration 클래스 안의 @Bean 메서드는 기본적으로 컨테이너의 싱글톤 보장을 위해 특별한 처리 대상이 된다.
  • Bean 등록 방식은 “어디서 만들기 편한가”보다 “생성 의도가 어디에 드러나야 하는가”로 고르는 편이 좋다.

헷갈리는 지점

  • @Component@Bean을 같은 등록 방식으로 보기 쉽다. 둘 다 결과적으로 Bean을 만들기 때문이다.
    • 핵심은 @Component는 클래스 자체를 스캔해 등록하고, @Bean은 메서드가 반환한 객체를 등록한다는 점이다.
    • 내가 작성한 애플리케이션 클래스는 컴포넌트 스캔, 외부 라이브러리 객체나 복잡한 생성 로직은 @Bean이 자연스럽다.
  • @Service@Component가 기능적으로 완전히 다르다고 생각하기 쉽다. 이름이 다른 애너테이션이기 때문이다.
    • 핵심은 등록 기능은 비슷하지만 의미가 다르다는 점이다.
    • @Service는 비즈니스 서비스 계층이라는 의도를 드러내고, @Repository는 persistence 계층 표시와 예외 변환 같은 후처리 대상과도 연결될 수 있다.
  • Bean 이름을 거의 신경 쓰지 않아도 된다고 느끼기 쉽다. 대부분 타입 주입이 잘 동작하기 때문이다.
    • 핵심은 같은 타입 후보가 여러 개 생기면 이름과 qualifier가 설계 정보가 된다는 점이다.
    • Bean 이름 규칙은 테스트, AOP Pointcut, 조건부 설정에서도 영향을 줄 수 있다.

확인 질문

  • @Component@Bean의 차이는 무엇인가?
    • @Component는 클래스를 스캔해 Bean으로 등록하고, @Bean은 설정 메서드가 반환한 객체를 Bean으로 등록한다.
  • @Service를 쓰는 이유는 무엇인가?
    • 단순 등록뿐 아니라 해당 클래스가 비즈니스 서비스 계층이라는 의미를 코드에 드러내기 위해서다.
  • 외부 라이브러리 객체를 Spring Bean으로 등록할 때 무엇이 자연스러운가?
    • @Configuration 클래스의 @Bean 메서드로 명시적으로 등록하는 방식이 자연스럽다.

Configuration과 Bean 메서드 프록시

3줄 요약

  • @Configuration 클래스는 Bean 정의의 출처이며, @Bean 메서드는 컨테이너가 관리할 객체를 생성한다.
  • Spring은 기본적으로 @Configuration 클래스를 프록시 처리해 같은 @Bean 메서드가 여러 번 호출되어도 같은 Bean을 반환하게 한다.
  • @Configuration(proxyBeanMethods=false)나 일반 @Component@Bean 메서드는 lite mode에 가까우므로 메서드 간 직접 호출을 의존성 표현 방식으로 쓰면 위험할 수 있다.

핵심 정리

  • @Bean 메서드는 객체를 생성, 설정, 초기화해서 Spring IoC Container가 관리하게 만드는 factory method 역할을 한다.
  • @Configuration은 해당 클래스의 주된 목적이 Bean 정의 제공이라는 의미다.
  • 같은 설정 클래스 안에서 @Bean 메서드가 다른 @Bean 메서드를 호출하면 순수 Java 관점에서는 매번 새 객체가 생길 것처럼 보인다.
  • Spring은 full @Configuration 모드에서 CGLIB 기반 프록시를 사용해 @Bean 메서드 호출을 컨테이너 조회로 우회시켜 싱글톤을 보장한다.
  • 기존 MVC 쪽 깊은 문서의 핵심도 이 지점이다. @Configuration 프록시는 AOP 부가 기능용 프록시가 아니라 Bean 생명주기와 싱글톤 보장을 위한 컨테이너 장치다.

헷갈리는 지점

  • @Bean 메서드는 그냥 Java 메서드이므로 호출할 때마다 새 객체가 생긴다고만 생각하기 쉽다. 순수 Java 코드로 보면 그렇게 보이기 때문이다.
    • 핵심은 full @Configuration에서는 Spring이 설정 클래스를 프록시 처리한다는 점이다.
    • 프록시가 이미 등록된 Bean을 반환하므로 inter-bean dependency에서도 싱글톤이 유지된다.
  • @Configuration 프록시와 AOP 프록시를 같은 것으로 보기 쉽다. 둘 다 프록시라는 단어를 쓰기 때문이다.
    • 핵심은 목적이 다르다는 점이다.
    • AOP 프록시는 메서드 호출에 부가 기능을 붙이고, @Configuration 프록시는 @Bean 메서드 호출을 컨테이너 생명주기 관리로 연결한다.
  • proxyBeanMethods=false를 항상 성능 최적화로만 보기 쉽다. CGLIB 프록시를 만들지 않으므로 가벼워 보이기 때문이다.
    • 핵심은 inter-bean method call이 필요한 설정인지 먼저 확인하는 것이다.
    • 서로 다른 @Bean 메서드 직접 호출로 의존성을 표현한다면 false는 subtle bug를 만들 수 있다.

확인 질문

  • @Configuration 클래스 안의 @Bean 메서드 호출이 특별한 이유는 무엇인가?
    • Spring이 설정 클래스를 프록시 처리해 이미 등록된 Bean을 반환함으로써 싱글톤을 보장하기 때문이다.
  • @Configuration 프록시와 AOP 프록시는 무엇이 다른가?
    • 전자는 Bean 생성과 싱글톤 보장을 위한 컨테이너 장치이고, 후자는 횡단 관심사를 적용하기 위한 호출 가로채기 장치다.
  • proxyBeanMethods=false를 쓰기 전에 무엇을 확인해야 하는가?
    • 같은 설정 클래스 안에서 @Bean 메서드끼리 직접 호출해 의존성을 표현하고 있지 않은지 확인해야 한다.