-
3 - 스프링 컨텍스트 빈 작성Spring/스프링 교과서 2026. 7. 23. 01:19
앞 장에서는 객체를 스프링 컨텍스트에 등록하는 방법을 살펴봤습니다. 이제 컨텍스트 안에
Person빈과Parrot빈이 있다고 가정해 보겠습니다.두 객체가 각각 존재하는 것만으로는
Person이Parrot을 사용할 수 없습니다.Person이 필요로 하는Parrot참조를 연결해야 합니다.빈을 등록하는 것이 객체를 준비하는 단계라면, 의존성 주입은 준비된 객체들이 함께 일하도록 연결하는 단계입니다.

스프링 컨테이너는 빈을 생성할 뿐 아니라 빈 사이의 의존성도 연결한다. 의존성 주입, DI는 객체가 의존성을 직접 생성하거나 컨테이너에서 찾는 대신 필요한 대상을 선언하고, 스프링 컨테이너가 그 값을 제공하는 방식입니다.
1. @Bean 메서드에서 다른 빈 연결하기
Java 설정 클래스에서는 두 가지 방식으로
@Bean사이의 관계를 표현할 수 있습니다.다른 @Bean 메서드를 직접 호출하기
@Configuration public class ProjectConfig { @Bean public Parrot parrot() { return new Parrot("Koko"); } @Bean public Person person() { return new Person("Ella", parrot()); } }person()이parrot()을 호출하므로 일반 자바 코드만 보면Parrot이 호출할 때마다 새로 생성될 것처럼 보입니다.기본 설정의
@Configuration클래스는 런타임 프록시 처리를 받습니다. 같은 설정 클래스 안에서 다른@Bean메서드를 호출하면 스프링이 호출을 가로채 컨테이너가 관리하는 빈을 반환합니다. 기본 singleton 범위라면Person에 연결되는 객체와 컨텍스트에서 조회한Parrot은 같은 인스턴스입니다.주의:
@Configuration(proxyBeanMethods = false)또는@Component안의@Bean처럼 lite mode로 처리되면 직접 호출은 평범한 자바 메서드 호출입니다. 이때는 호출할 때마다 새 인스턴스가 생성될 수 있습니다.@Bean 메서드 매개변수로 받기
@Configuration public class ProjectConfig { @Bean public Parrot parrot() { return new Parrot("Koko"); } @Bean public Person person(Parrot parrot) { return new Person("Ella", parrot); } }person()은 자신이Parrot을 필요로 한다는 사실만 매개변수로 선언합니다. 스프링은 컨텍스트에서 주입 후보를 찾아 해당 매개변수에 제공합니다.의존성이 메서드 시그니처에 드러나고 lite mode의 직접 호출 문제도 피할 수 있어 의도가 더 명확합니다. 의존 대상이 다른
@Bean에서 만들어졌는지, 컴포넌트 스캔으로 등록됐는지도Person생성 로직이 알 필요가 없습니다.2. @Autowired로 컴포넌트의 의존성 주입하기
스프링은 생성자, 필드, 메서드에
@Autowired를 적용해 타입에 맞는 빈을 찾고 주입할 수 있습니다.
필수 의존성은 생성자 주입으로 드러내는 편이 명확하다. 필드 주입
@Component public class Person { @Autowired private Parrot parrot; }코드는 짧지만 객체가 어떤 의존성을 반드시 필요로 하는지 생성자에서 알기 어렵습니다. 필드를
final로 만들 수 없고, 스프링 컨테이너 없이 단위 테스트를 작성할 때 의존성을 넣기도 불편합니다.생성자 주입
@Component public class Person { private final Parrot parrot; public Person(Parrot parrot) { this.parrot = parrot; } }생성자 주입은 의존성을 객체 생성 조건으로 만듭니다. 필드를
final로 선언할 수 있고, 테스트에서도new Person(fakeParrot)처럼 스프링 없이 의존성을 명시적으로 전달할 수 있습니다.대상 클래스에 생성자가 하나뿐이면 현재 스프링에서는 생성자의
@Autowired를 생략해도 됩니다.setter 주입
@Component public class Person { private Parrot parrot; @Autowired public void setParrot(Parrot parrot) { this.parrot = parrot; } }setter 주입은 객체를 만든 뒤 의존성을 설정합니다. 선택적으로 바꿀 수 있어야 하는 프로퍼티에는 사용할 수 있지만, 필수 의존성이라면 불완전한 객체가 잠시 존재할 수 있고
final로 고정할 수도 없습니다.3. 순환 의존성은 왜 위험한가
Person을 만들려면Parrot이 필요하고,Parrot을 만들려면 다시Person이 필요하다고 해 보겠습니다.Person ──필요──> Parrot ^ │ └──────필요──────┘두 빈이 생성자로 서로를 요구하면 어느 쪽도 먼저 완성할 수 없습니다. 스프링은 이 생성자 순환 참조를 감지하고
BeanCurrentlyInCreationException으로 실패합니다.setter 주입을 이용하면 일부 순환 참조를 기술적으로 구성할 수 있지만, 한 객체가 완전히 초기화되기 전에 다른 객체에 노출될 수 있습니다. 순환 의존성은 두 클래스의 책임이 강하게 얽혔다는 설계 신호이므로 공통 책임 분리, 이벤트 발행, 협력 방향 재설계로 끊는 편이 좋습니다.
4. 같은 타입의 빈이 여러 개라면
다음처럼
Parrot타입의 빈이 두 개면 타입만으로는 어떤 빈을 주입할지 결정할 수 없습니다.@Bean public Parrot koko() { return new Parrot("Koko"); } @Bean public Parrot miki() { return new Parrot("Miki"); }기본 후보는 @Primary
@Bean @Primary public Parrot koko() { return new Parrot("Koko"); }단일 값을 주입하는 지점에 같은 타입 후보가 여러 개 있을 때 정확히 하나의
@Primary빈이 있으면 그 빈이 우선됩니다.특정 후보는 @Qualifier
@Component public class Person { private final Parrot parrot; public Person(@Qualifier("miki") Parrot parrot) { this.parrot = parrot; } }@Qualifier는 타입으로 찾은 후보를 다시 좁혀 주입 의도를 명시합니다. 매개변수 이름과 빈 이름이 우연히 일치하는 규칙에 기대기보다 선택 기준을 코드에 드러낼 수 있습니다.후보가 하나로 좁혀지지 않으면 스프링은 임의로 선택하지 않고 예외를 발생시킵니다. 잘못된 객체가 조용히 주입되는 것보다 안전한 실패입니다.
마무리
스프링 컨텍스트는 빈을 보관하는 공간에 그치지 않습니다. 각 빈이 필요로 하는 협력 대상을 찾아 객체 그래프를 조립합니다.
@Bean메서드 매개변수는 설정 클래스에서 의존성을 명확하게 표현합니다.- 컴포넌트의 필수 의존성은 생성자 주입으로 드러내는 것이 좋습니다.
- 단일 생성자라면
@Autowired를 생략할 수 있습니다. - 순환 의존성은 우회하기보다 객체의 책임과 협력 방향을 다시 살펴봐야 합니다.
- 같은 타입 후보가 여러 개면
@Primary또는@Qualifier로 선택 의도를 표현합니다.
객체를 빈으로 등록하는 것이 스프링 구성의 시작이라면, 빈 사이의 관계를 분명하게 작성하는 것은 실제 애플리케이션을 조립하는 단계입니다.
참고한 문서
'Spring > 스프링 교과서' 카테고리의 다른 글
2 - 스프링 컨텍스트 빈 정의 (0) 2026.07.13 1 - 스프링 생태계 (0) 2026.07.13