자바 백엔드 입문 19편. @Configuration·@Bean으로 Bean을 명시적으로 등록하는 Java Config 방식. 외부 라이브러리 클래스를 Bean으로 만드는 가장 흔한 패턴을 요리 레시피 비유로 풀어쓴 학습 노트.
이 글은 자바 백엔드 입문 시리즈 59편 중 19편이에요. 18편에서 @Component·@Autowired 어노테이션 기반 설정을 다뤘다면, 이번 19편은 또 다른 Bean 등록 방식 — @Configuration·@Bean Java Config를 풀어 갑니다. "내가 직접 작성한 클래스가 아니라 외부 라이브러리의 객체를 Bean으로 만들고 싶을 때" 가 가장 흔한 사용처예요.
Java Config가 어렵게 들리는 이유
12편의 @Component 방식이 익숙해진 후 13편을 만나면 "왜 또 다른 방식?" 가 헷갈려요.
첫째, @Component 만으로는 안 되는 시나리오가 있어요. 외부 라이브러리(Jackson·RestTemplate·DataSource)의 클래스에는 @Component 를 박을 수 없거든요. 그 클래스 코드 자체를 우리가 못 고치니까.
둘째, @Configuration 과 @Bean 의 역할 분담이 처음엔 안 잡혀요.
이 글에서는 요리 레시피 비유로 풀어 가요. @Configuration = "이 클래스는 요리 레시피 모음집", @Bean = "이 메서드는 한 가지 요리(객체) 만드는 레시피". 끝까지 따라오시면 두 어노테이션이 한 그림에 들어와요.
@Configuration — 레시피 모음집
@Configuration 은 "이 클래스는 Bean을 만드는 레시피들의 모음집이다" 선언이에요. 평범한 자바 클래스에 박으면 — Spring이 시작 시점에 "이 안의 @Bean 메서드들을 다 호출해서 그 반환값을 Bean으로 등록" 하는 처리를 합니다.
@Configuration
public class AppConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
return mapper;
}
}
이 코드를 풀면 — AppConfig 클래스 안에 두 개의 @Bean 메서드가 박혀 있어요. Spring이 시작될 때 두 메서드를 호출해서 반환값(RestTemplate 객체, ObjectMapper 객체)을 컨테이너에 Bean으로 등록해요. Bean 이름은 기본으로 메서드 이름과 동일 — restTemplate, objectMapper.
@Bean — 한 레시피 한 메서드
@Bean 은 "이 메서드의 반환값을 Bean으로 등록해 줘" 메모예요. 메서드 안에서 우리가 직접 new 로 객체를 만들고 설정 처리한 뒤 반환하면, 그 반환값이 Spring 컨테이너에 들어갑니다.
@Component 와의 결정적 차이 — @Component 는 클래스 자체에 박는 메모, @Bean 은 메서드에 박는 메모. 그래서 외부 라이브러리 클래스(우리 코드가 아닌 것)도 Bean으로 만들 수 있어요. "코드 안에서 new RestTemplate() 하고 그 결과를 반환" 하면 되니까요.
@Component vs @Bean — 언제 어느 걸?
이 둘이 가장 헷갈리는 부분이에요. 룰은 단순.
| 시나리오 | 권장 방식 |
|---|---|
| 내가 작성한 클래스가 Spring Bean이 돼야 할 때 | @Component/@Service/@Repository |
| 외부 라이브러리 클래스(Jackson·DataSource 등)를 Bean으로 등록할 때 | @Configuration + @Bean |
| Bean 생성 로직이 복잡한 경우 (다른 객체 참고·조건 분기) | @Configuration + @Bean |
| 같은 클래스로 여러 Bean 만들고 싶을 때 (커넥션 풀 2개 등) | @Configuration + @Bean 여러 개 |
90% 케이스에서 우리가 작성한 클래스 = @Component, 외부 라이브러리 = @Bean. 거의 다른 선택지가 없어요.
의존성 주입을 @Bean 메서드에서 받기
@Bean 메서드 안에서 다른 Bean이 필요하면 — 메서드 매개변수로 받으면 됩니다.
@Configuration
public class AppConfig {
@Bean
public DataSource dataSource() {
HikariConfig cfg = new HikariConfig();
cfg.setJdbcUrl("jdbc:postgresql://localhost:5432/myshop");
return new HikariDataSource(cfg);
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) { // ← DataSource 자동 주입
return new JdbcTemplate(dataSource);
}
}
jdbcTemplate 메서드가 DataSource 매개변수를 선언하면, Spring이 컨테이너에서 DataSource Bean을 찾아 자동으로 넘겨줘요. 이 안에 @Autowired 같은 어노테이션 박을 필요 X — Java Config 메서드 매개변수는 자동으로 의존성 주입 대상.
@Configuration 클래스 안에서 jdbcTemplate(dataSource()) 같이 다른 @Bean 메서드를 직접 호출해도 — Spring이 마법처럼 같은 Bean을 반환합니다. 평범한 메서드 호출처럼 새 객체를 만드는 게 아니에요. CGLib 프록시가 가로채서 컨테이너에서 꺼내준 결과. 신기하지만 입문에선 매개변수로 받는 패턴이 더 깔끔해요.
@Bean 메서드의 다양한 옵션
@Bean 메서드에 여러 옵션을 박을 수 있어요. 자주 쓰이는 4가지.
@Bean(name = "primaryDataSource") // Bean 이름 직접 지정
@Bean(initMethod = "init") // 초기화 호출할 메서드
@Bean(destroyMethod = "close") // 소멸 시 호출할 메서드
@Bean
@Primary // 기본 Bean으로 지정
@Scope("prototype") // 매번 새 객체 (14편에서)
public DataSource dataSource() { ... }
destroyMethod = "close" 는 "컨테이너 종료 시 이 메서드 자동 호출" — 커넥션 풀 같은 리소스가 정상 종료되게 보장해줘요. AutoCloseable 구현 클래스는 자동으로 처리되기도 합니다.
@Configuration vs 일반 클래스 + @Bean
@Configuration 안 박힌 평범한 @Component 클래스에 @Bean 메서드 박아도 — Bean 등록은 됩니다. 다만 CGLib 프록시 처리가 안 돼서, 클래스 내부에서 다른 @Bean 메서드 직접 호출 시 "매번 새 객체" 가 만들어져요.
// 비권장 패턴
@Component
public class BadConfig {
@Bean public Foo foo() { return new Foo(); }
@Bean public Bar bar() { return new Bar(foo()); } // foo() 호출 시 새 Foo
}
// 권장 패턴
@Configuration
public class GoodConfig {
@Bean public Foo foo() { return new Foo(); }
@Bean public Bar bar(Foo foo) { return new Bar(foo); } // 매개변수로 받음
}
@Configuration + 매개변수로 받기. 이게 표준이에요.
@Import — Configuration 합치기
여러 @Configuration 클래스로 나눈 후, 메인 Config에서 다 가져올 때 @Import 를 써요.
@Configuration
@Import({DataSourceConfig.class, WebConfig.class, SecurityConfig.class})
public class AppConfig {
@Bean public RestTemplate restTemplate() { return new RestTemplate(); }
}
이러면 AppConfig 하나만 등록해도 DataSourceConfig·WebConfig·SecurityConfig 안의 모든 @Bean 이 자동 합쳐져요. 대형 시스템에서 "역할별로 Config 파일 분리 + 메인에서 통합" 패턴이 표준입니다.
@ComponentScan과 Java Config 함께 쓰기
@Component 와 @Bean 은 같이 써요. 한 프로젝트에서 자주 보는 패턴:
@Configuration
@ComponentScan(basePackages = "com.example.myshop")
public class AppConfig {
// 외부 라이브러리 객체 = @Bean
@Bean public RestTemplate restTemplate() { return new RestTemplate(); }
@Bean public DataSource dataSource() { ... }
// 우리 클래스(OrderService 등)는 @Component 박혀 있고 자동 스캔됨
}
@SpringBootApplication 안에 @ComponentScan + @Configuration 이 이미 들어 있어요. 그래서 Spring Boot 프로젝트는 메인 클래스 한 개로 두 방식이 자동 결합됩니다. 우리는 "내가 작성한 건 @Service, 외부 라이브러리는 메인 클래스 안에 @Bean 메서드 추가" 만 신경 쓰면 끝.
한 줄 정리 — @Configuration = Bean 레시피 모음집, @Bean = 한 객체 만드는 메서드. 외부 라이브러리 객체를 Bean으로 등록할 때 표준. @Component 와 둘 다 같이 자주 씀.
시험 직전 한 번 더 — Java Config 입문자가 매번 헷갈리는 것
@Configuration= Bean 레시피 모음집 선언@Bean= 메서드의 반환값을 Bean으로 등록하는 메모@Component= 클래스 자체에 박는 메모,@Bean= 메서드에 박는 메모- 내 클래스 =
@Component/@Service/@Repository/@Controller - 외부 라이브러리 클래스 =
@Configuration+@Bean - Bean 이름 기본 = 메서드 이름 그대로
@Bean(name = "...")으로 이름 직접 지정- 의존성 주입 =
@Bean메서드 매개변수로 받음 (자동 주입) @Configuration+ 매개변수 패턴이 권장 표준@Configuration안의@Bean메서드를 직접 호출해도 — CGLib 프록시 덕분에 컨테이너 Bean 반환- 일반
@Component클래스 안@Bean메서드 직접 호출 시 — 매번 새 객체 만들어짐 (비권장) @Bean(initMethod = "init", destroyMethod = "close")— 생명주기 콜백 지정@Bean+@Primary— 같은 타입 여러 개일 때 기본값 지정@Bean+@Scope("prototype")— 매번 새 객체 (14편에서 깊이)@Import({Config1.class, Config2.class})= 여러 Config 합치기- 대형 시스템 표준 = 역할별 Config 분리 + 메인
@Import @SpringBootApplication안에@Configuration+@ComponentScan이미 들어 있음- 따라서 메인 클래스 자체에
@Bean메서드 박아도 OK - 같은 컨테이너에
@Component와@BeanBean이 섞여 있는 게 일반 패턴 - 가장 흔한
@Bean등록 예 =RestTemplate·ObjectMapper·DataSource·PasswordEncoder
시리즈 다른 편 (앞뒤 글 모음)
이전 글:
- 14편 — 첫 Bean 만들기 Hello Spring
- 15편 — 의존성 주입이 왜 필요한가
- 16편 — Bean이란 일반 객체와의 차이
- 17편 — ApplicationContext 컨테이너 본체
- 18편 — @Component @Autowired 한 번에
다음 글: