본문 바로가기
카테고리 없음

@configuration 작동원리

by sudong 2025. 11. 15.

“이게 왜 Rabbit 설정이지?”

@Configuration부터 RabbitAdmin까지 한 번에 이해하기

RabbitMQ 설정을 하다 보면 이런 생각 한 번쯤 든다:

“그냥 @Configuration 클래스 만들어서 Queue, Exchange, Binding을 @Bean으로 올려놨을 뿐인데…
스프링은 이게 RabbitMQ 설정이라는 걸 도대체 어떻게 아는 거지?

나도 처음엔 이 부분이 굉장히 찝찝했는데,
한 번 구조를 쫙 뜯어보고 나니까
Spring 전체 동작 방식이 한 방에 연결되더라.

이 글은 바로 그 흐름을 정리한 글이다.
RabbitMQ를 예로 들지만, 사실상 @Configuration / @Bean / Auto-Configuration 기본 원리를 이해하는 내용이라고 보면 된다.


1. @Configuration의 본질적인 역할

먼저 가장 중요한 오해부터 잡고 가자.

❌ @Configuration = “Rabbit 설정이다”
✅ @Configuration = “여기 스프링 빈 정의 모음이 있다”

@Configuration의 역할은 딱 하나다.

  • 이 클래스 안에는 @Bean 메서드들이 있으니,
  • 스프링 컨테이너야, 이 메서드들을 실행해서 리턴값을 빈으로 등록해줘.

예를 들어:

@Configuration
public class RabbitConfig {

    @Bean
    public Queue exampleQueue() {
        return new Queue("example.queue", true);
    }
}
 

스프링이 보는 건:

  • “아, RabbitConfig 클래스에 @Configuration이 붙었네?”
  • “그 안에 @Bean 메서드(exampleQueue)가 있네?”
  • “이 메서드 리턴값을 스프링 빈으로 등록해야겠구나.”

여기까지는 RabbitMQ라는 단어는 단 한 글자도 안 나온다.
스프링 입장에서는 이게 Rabbit 설정인지, JPA 설정인지, Redis 설정인지 관심 없다.

그걸 정의하는 건 리턴 타입과 클래스패스(어떤 라이브러리가 올라와 있느냐) 쪽이다.


2. @Bean 메서드와 스프링 컨테이너

@Bean은 “이 메서드의 리턴값을 빈으로 등록해라”라는 선언이다.

간단한 예:

@Configuration
public class AppConfig {

    @Bean
    public Object myObject() {
        return new Object();
    }
}
 

스프링이 하는 일은:

  • 컨텍스트 초기화 시점에 AppConfig를 스캔
  • @Bean이 붙은 myObject() 메서드를 호출
  • 리턴된 Object 인스턴스를 컨테이너에 등록
    • 이름: 기본값은 메서드 이름 ("myObject")
    • 스코프: 기본은 싱글톤

RabbitMQ 설정도 똑같다:

@Bean
public Queue exampleQueue() {
    return new Queue("example.queue", true);
}
 

스프링 입장에서 이건 그냥:

  • 타입: org.springframework.amqp.core.Queue
  • 이름: exampleQueue
  • 역할: 나중에 누군가 이 타입/이름의 빈을 필요로 하면 주입해줄 인스턴스

여기까지도 RabbitMQ 서버와는 아직 1도 연결되지 않았다.
그냥 “스프링 내부에 Queue 객체 하나를 싱글톤으로 들고 있는 상태”일 뿐이다.


3. Spring Boot Auto-Configuration 개념

여기서 등장하는 개념이 바로 Auto-Configuration(자동 설정)이다.

📌 용어 정리 – Auto-Configuration
Spring Boot가 클래스패스와 환경을 보고 자동으로 여러 설정을 구성해주는 메커니즘.
예: gradle에 spring-boot-starter-web 의존성을 추가하면
DispatcherServlet, ObjectMapper, MessageConverter 등등을 알아서 등록해주는 것.

작동 방식의 핵심은 이거다:

  • 특정 라이브러리가 클래스패스에 있으면
  • 그에 해당하는 @Configuration 클래스(자동 설정)가 조건부로 활성화된다.
  • 이 설정 안에서 각종 인프라 빈들을 만들어준다.
    • 예: DataSource, EntityManagerFactory, RabbitTemplate, KafkaTemplate 등

즉, 우리가 코드로 직접 적지 않아도:

  • spring-boot-starter-XXX를 추가하면
  • 그에 맞는 infra 빈들이 쫙 깔리는 구조다.

이게 RabbitMQ에도 그대로 적용된다.

클래스패스: JVM이 “클래스(.class)나 리소스 파일(.properties 등)을 어디서 찾을지 적어 놓은 경로 목록”

4. AMQP 스타터와 RabbitAutoConfiguration

RabbitMQ를 Spring Boot에서 쓸 때는 보통 이렇게 의존성을 추가한다.

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-amqp'
}
 

이 한 줄이 하는 일은 생각보다 크다. 😈

spring-boot-starter-amqp 안에는:

  • spring-rabbit (AMQP 관련 핵심 라이브러리)
  • RabbitAutoConfiguration (자동 설정 클래스)

같은 것들이 들어 있다.

RabbitAutoConfiguration은 대략 이런 느낌이다 (의미만):

@Configuration
@ConditionalOnClass(RabbitTemplate.class) // 클래스패스에 spring-rabbit 이 있을 때만
@EnableConfigurationProperties(RabbitProperties.class)
public class RabbitAutoConfiguration {

    @Bean
    public ConnectionFactory connectionFactory(...) { ... }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory cf) {
        return new RabbitTemplate(cf);
    }

    @Bean
    public AmqpAdmin amqpAdmin(ConnectionFactory cf) {
        return new RabbitAdmin(cf);
    }
}
 

여기서 중요한 포인트:

  • spring-boot-starter-amqp를 추가했다
    → RabbitAutoConfiguration이 활성화된다.
  • 그 안에서:
    • ConnectionFactory
    • RabbitTemplate
    • AmqpAdmin(RabbitAdmin)
      같은 핵심 인프라 빈들이 자동으로 등록된다.

이제야 비로소 “이 프로젝트는 RabbitMQ 인프라를 쓸 준비가 됐다”는 상태가 된다.


5. RabbitAdmin의 역할

이제 진짜 핵심 캐릭터 등장이다: RabbitAdmin

📌 RabbitAdmin이 하는 일

  • 스프링 컨텍스트에 존재하는
    Queue, Exchange, Binding 타입의 빈들을 찾아서
  • 실제 RabbitMQ 브로커에 declare(생성/바인딩)를 날려주는 관리자 역할

조금 감각적으로 표현하면:

  • 너는 그냥 @Configuration에서
    • @Bean Queue
    • @Bean DirectExchange
    • @Bean Binding
      를 만들어 놓는다.
  • 그러면 스프링 컨텍스트에는 그냥 자바 객체들이 들어 있다.
  • RabbitAdmin은 초기화 시점에:
    • “컨텍스트에 Queue 타입 빈 있나?”
    • “Exchange 타입 빈 있나?”
    • “Binding 타입 빈 있나?”
      를 스캔하고
    • 있으면 RabbitMQ 서버에
      • queueDeclare
      • exchangeDeclare
      • queueBind
        를 호출해서 진짜 객체로 만들어준다.

즉,

  • 네 코드 → “설계도(Queue/Exchange/Binding Bean)”
  • RabbitAdmin → “설계도를 보고 RabbitMQ 서버에 실제로 건물(큐/익스체인지)을 만드는 시공사”

라고 보면 된다. 🏗️


6. Queue / Exchange / Binding Bean → 브로커 설정으로 변환되는 흐름

이제 지금까지의 내용을 Step-by-Step 시퀀스로 정리해보자.

🧩 Step 1. rabbitmq 설정 코드

@Configuration
public class RabbitConfig {

    public static final String EXCHANGE_NAME = "example.direct.exchange";
    public static final String QUEUE_NAME = "example.queue";
    public static final String ROUTING_KEY = "example.routing";

    @Bean
    public Queue exampleQueue() {
        return new Queue(QUEUE_NAME, true);
    }

    @Bean
    public DirectExchange exampleExchange() {
        return new DirectExchange(EXCHANGE_NAME, true, false);
    }

    @Bean
    public Binding exampleBinding(Queue exampleQueue, DirectExchange exampleExchange) {
        return BindingBuilder
                .bind(exampleQueue)
                .to(exampleExchange)
                .with(ROUTING_KEY);
    }
}
 

이 상태에서 스프링 컨테이너 입장은 단순하다:

  • 빈 목록에 다음 세 개가 올라왔다.
    • 타입 Queue인 exampleQueue
    • 타입 DirectExchange인 exampleExchange
    • 타입 Binding인 exampleBinding

🧩 Step 2. Auto-Configuration이 RabbitAdmin 생성

spring-boot-starter-amqp 덕분에:

  • RabbitAutoConfiguration이 활성화되고
  • AmqpAdmin amqpAdmin = new RabbitAdmin(connectionFactory) 같은 Bean이 컨텍스트에 등록된다.

즉, 컨텍스트 안에는 이제:

  • Queue / Exchange / Binding 빈들 (네가 정의한 도메인 설정)
  • RabbitAdmin 빈 (Boot가 자동으로 구성한 infra)

이 둘이 같이 존재하게 된다.

🧩 Step 3. RabbitAdmin이 Bean들을 스캔해서 브로커에 Declare

RabbitAdmin의 초기화 로직(간략화):

  1. 스프링 컨텍스트에서:
    • 모든 Queue 타입 Bean 찾기
    • 모든 Exchange 타입 Bean 찾기
    • 모든 Binding 타입 Bean 찾기
  2. 순서대로 RabbitMQ 서버에:
    • channel.queueDeclare(...)
    • channel.exchangeDeclare(...)
    • channel.queueBind(queue, exchange, routingKey)
      등을 호출해서 선언

결과적으로 RabbitMQ 서버 내부에는 이런 구조가 생긴다:

  • example.queue 라는 큐 생성
  • example.direct.exchange 라는 익스체인지 생성
  • 둘 사이에 routingKey = example.routing으로 바인딩

즉, 우리가 작성한 @Configuration 코드는 단지 “설정 객체를 빈으로 올리는 작업”일 뿐이고,
그걸 실제 RabbitMQ 설정으로 바꿔주는 건 RabbitAdmin의 역할이다.


7. “이 클래스가 Rabbit 설정이다”를 인식하는 방식에 대한 오해 풀기

여기서 제일 중요한 결론을 정리하자.

❌ 스프링이 RabbitConfig라는 클래스명을 보고
“아, 이건 Rabbit 설정 클래스구나!” 라고 인식하는 게 아니다.

✅ 스프링은 그냥:

  • @Configuration → “여긴 @Bean이 있다.”
  • @Bean 메서드 → “이 리턴값을 빈으로 등록하자.”
  • Auto-Configuration → “RabbitAdmin 같은 infra 빈을 올리자.”
  • RabbitAdmin → “컨텍스트에 있는 Queue/Exchange/Binding 타입을 찾아서
    RabbitMQ 서버에 declare 하자.”
    라는 역할 분리를 통해 동작한다.

즉, 이 질문의 답이 된다:

“@Configuration 붙어있으면 이걸 어떻게 RabbitMQ 관련 설정이라고 아는 거야?”

정확한 답은 이거다:

  • 스프링은 @Configuration을 보고 “이게 Rabbit 설정이다”라고 인식하는 게 아니다.
  • 단지:
    • 너가 Queue, Exchange, Binding 같은 Rabbit 관련 타입의 Bean을 만들었고,
    • Auto-Configuration 덕분에 RabbitAdmin이라는 infra Bean이 함께 존재하니까,
    • RabbitAdmin이 그 Bean들을 보고
      “아, 이건 내가 RabbitMQ 서버에 선언해야 할 대상들이구나” 하고 처리하는 것이다.

결국 “Rabbit 설정인지의 여부”는:

  • 클래스 이름도 아니고,
  • @Configuration 어노테이션도 아니고,
  • 패키지 이름도 아니다.

오로지:

  • 컨텍스트 안에 어떤 타입의 빈들이 있고
  • 어떤 Auto-Configuration/infra 빈이 함께 생성되었느냐

이 두 가지 조합으로 결정된다.


이 정도까지 이해하고 나면,
RabbitMQ뿐 아니라 JPA, Kafka, Redis, Security 같은 다른 영역도 똑같은 눈으로 보이기 시작한다.

  • “아, 결국 도메인 설정(@Configuration + @Bean)
    Auto-Configuration 기반 infra 설정
    역할 분담해서 전체 시스템을 구성하는 거구나.”