스프링 IoC 컨테이너가 관리하는 객체를 의미
싱글톤(Singleton)은 클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴
애플리케이션 전체에서 해당 클래스의 객체를 하나만 만들고, 그것을 공유해서 사용함
싱글톤을 왜 사용할까?
- 메모리 효율성: 최초 한 번만 객체를 생성하므로 메모리 낭비를 방지할 수 있습니다.
- 데이터 공유와 일관성: 시스템 전반의 설정 정보나 공통 자원을 관리할 때 데이터의 일관성을 유지하기 쉽습니다.

재사용되거나 교체 가능한 비즈니스 로직/인프라
// ✅ Spring Bean으로 관리
@Service
public class UserService { } // 비즈니스 로직
@Repository
public class UserRepository { } // 데이터 접근
@Component
public class EmailSender { } // 인프라
@Controller
public class UserController { } // 컨트롤러
매번 새로운 상태를 가지는 데이터 객체
// ❌ Bean으로 만들면 안 됨!
public class User {
private String name;
private int age;
}
public class OrderRequest {
private Long userId;
private List<Item> items;
}
public class SearchCondition {
private String keyword;
private LocalDate startDate;
}
어노테이션 합성은 여러 개의 어노테이션을 조합하여 하나의 새로운 어노테이션을 만드는 것을 의미
스프링은 어노테이션을 분석할 때, 해당 어노테이션 위에 붙어있는 다른 어노테이션(메타 어노테이션)까지 재귀적으로 탐색하여 그 기능을 모두 적용해 줌
어노테이션도 어노테이션을 가질 수 있음!
자동 등록과 수동 등록 방식이 있음
Spring이 @Component 클래스를 찾아서 Bean으로 등록하는 방식
Spring의 @Configuration 클래스와 @Bean 메소드를 사용하여 명시적으로 Bean을 등록하는 방식
기본적으로 자동 등로이 우선, 불가피한 경우에만 수동 등록 활용
Spring에서 의존성 주입은 '생성자 주입', '세터 주입', '필드 주입' 방식으로 구현할 수 있음
@Autowired를 필드에 직접 선언하여 의존성을 주입받는 방식
-> 더 이상 사용하지 않음!
여러 문제가 있지만, 대표적으로 final 키워드 사용이 불가능하기 때문에 변수에 할당된 객체를 아래의 코드 예처럼 null 혹은 다른 객체로 바꿀 수 있어 안전하지 않음
Setter 메소드를 통해 의존성을 주입받는 방식
-> 더 이상 사용하지 않음!
필드 주입 방식과 같은 문제가 있어 안전하지 않음
생성자를 통해 의존성을 주입받는 방식
-> 권장 방식
@Service
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
}
private final MemberRepository memberRepository;final로 선언하여 런타임에 의존성이 변경될 위험이 없음 이를 불변성(Immutability) 보장이라고도 함
스프링 컨테이너에 똑같은 타입의 Bean이 2개 이상 등록되면 어떻게 될까?
@Primary는 여러 빈 중에서 우선적으로 선택될 기본(Default) 빈을 지정하는 어노테이션
-> 별다른 설정이 없다면 @Primary가 붙은 빈이 자동으로 주입됨
@Qualifier라는 이름으로 특정 빈을 직접 지정하여 주입하는 어노테이션
@Primary보다 우선순위가 높으며, 더 구체적인 선택이 필요할 때 사용
Bean 스코프는 스프링 빈이 얼마나 오래, 그리고 어떻게 존재할지를 정의하는 개념
기본값은 싱글톤
스프링 컨테이너가 시작될 때 단 한 번만 생성되고, 애플리케이션이 끝날 때까지 계속 재사용되는 방식
대부분의 빈은 싱글톤으로 관리되며, 메모리 효율성이 매우 좋음
@Scope("prototype")으로 지정된 빈은, 요청이 올 때마다 계속 새로운 객체를 생성하여 반환
스프링 컨테이너는 생성만 책임지고, 그 이후의 관리는 하지 않음

@PostConstruct 어노테이션이 붙은 메서드는 빈의 생성과 모든 의존성 주입이 완료된 직후에 딱 한 번 호출됨@PreDestroy는 스프링 컨테이너에서 빈이 제거되기 직전에 호출@Component
public class MusicPlayer {
private List<String> playlist = new ArrayList<>();
// 초기화 콜백: 의존성 주입이 끝난 후 실행
@PostConstruct
public void loadPlaylist() {
System.out.println("--- @PostConstruct 호출 ---");
playlist.add("아이유 - 라일락");
playlist.add("BTS - Dynamite");
System.out.println("플레이리스트 로딩 완료!");
}
// 소멸 전 콜백: 빈이 사라지기 직전 실행
@PreDestroy
public void saveProgress() {
System.out.println("--- @PreDestroy 호출 ---");
System.out.println("뮤직 플레이어를 종료합니다...");
}
}
Bean을 그냥 스프링이 만들어주는 객체라고만 알고 있었는데, 오늘 정리하면서 생명주기까지 포함한 관리 대상이라는 걸 확실히 이해했다.
싱글톤이 “객체 1개”라는 의미에서 끝나는 게 아니라, 같은 객체를 여러 곳에서 공유하니까 상태를 가지면 위험해질 수 있다는 점이 인상 깊었다. 그래서 Service/Repository는 보통 stateless 하게 만드는 이유가 납득됐다.
예전엔 new로 객체 만들면 편하다고 생각했는데, IoC/DI를 쓰면 교체(확장), 테스트, 유지보수가 쉬워지는 구조가 된다는 걸 체감했다. “편한 코드”보다 “바꾸기 쉬운 코드”가 더 중요하다는 느낌.
DI 방식 중 필드 주입이 위험한 이유를 “그냥 쓰지 말라고 해서”가 아니라, final 불가 -> 불변성 깨짐 -> 테스트/안전성 떨어짐으로 논리적으로 이해하게 됐다. 이제는 생성자 주입이 자연스럽게 기본 선택이 될 것 같다.
@Primary와 @Qualifier는 단순한 문법이 아니라, Bean이 여러 개일 때 스프링이 어떤 기준으로 선택할지 ‘명확한 의도’를 코드로 남기는 장치라는 걸 알게 됐다.
@PostConstruct, @PreDestroy는 “있으면 편한 기능” 정도로 봤는데, 실제로는 외부 리소스 초기화/정리 같은 안정성을 책임지는 포인트라서 운영 관점에서 더 중요하다는 걸 깨달았다.