예전엔 XML 파일에 빈을 하나하나 적었어요. 요즘은 클래스에 어노테이션(@) 한 줄만 붙이면 스프링이 알아서 빈으로 등록해줘요. 이번 장은 @Component 계열과 컴포넌트 스캔이 핵심입니다.
CH 03에서 배웠듯이, 스프링은 빈(Bean)을 컨테이너에 등록해두고 관리해요. 그런데 "어떤 클래스를 빈으로 등록할지"를 스프링에게 알려주는 방법이 필요하죠.
🏷️ 번외편 — 스프링 애너테이션 총정리 (컴포넌트 등록·주입·웹/REST·JPA·검증·보안까지 한곳에) 새 탭 ↗new 하지 않고 스프링에게 맡긴 "부품"이라고 생각하면 돼요. (CH 03에서 다뤘어요.)applicationContext.xml 같은 파일에 빈을 하나하나 손으로 등록했어요. 클래스가 많아지면 XML도 길어지고 관리가 번거로웠어요.
<!-- 예전 XML 방식 (참고용) -->
<bean id="memberService" class="com.study.MemberService">
<constructor-arg ref="memberRepository"/>
</bean>
<bean id="memberRepository" class="com.study.MemberRepository"/>
@Service // ← 이 한 줄이면 빈 등록 끝!
public class MemberService {
// ...
}
💡 어노테이션(Annotation)은 코드에 붙이는 "메타 정보 표시"예요. 스프링은 이 표시를 읽어서 "아, 이 클래스는 빈으로 만들어야겠구나"라고 판단해요.
@이름 형태로 딱 붙여서 "이건 이런 용도야"라고 표시만 해둬요. 그러면 스프링이 실행할 때 이 스티커들을 하나하나 읽어보고 "아, 이 클래스는 빈으로 등록하라는 거구나" 하고 알아서 처리해줘요. 노트에 포스트잇을 붙여 표시해두는 것과 똑같답니다.스프링이 빈으로 등록할 클래스에 붙이는 대표 어노테이션이 @Component예요. 그런데 실무에서는 클래스가 어떤 역할(계층)을 하는지 드러내려고, @Component의 특수화된 별칭들을 씁니다.
@Controller, @Service, @Repository는 모두 내부적으로 @Component를 포함하고 있어요. 그래서 빈으로 등록되는 동작은 셋 다 똑같아요. 다만 이름을 달리 써서 그 클래스의 역할(의미)을 코드에서 바로 알 수 있게 해줘요.
즉 @Service를 @Component로 바꿔도 빈 등록은 동일하지만, "이건 비즈니스 로직 계층이다"라는 의미가 사라져요. 그래서 역할에 맞는 어노테이션을 쓰는 게 좋아요.
| 어노테이션 | 담당 계층 / 역할 |
|---|---|
@Component | 가장 일반적인 빈. 특정 계층에 속하지 않는 공용 컴포넌트에 사용. |
@Controller | 웹 요청 처리 계층. 사용자의 요청을 받아 응답을 돌려줌. |
@Service | 비즈니스 로직 계층. 핵심 업무 처리를 담당. |
@Repository | DB 접근(데이터) 계층. 데이터베이스와의 저장·조회를 담당. |
클래스에 @Component 계열을 붙였다고 자동으로 등록되는 건 아니에요. 스프링이 지정된 패키지를 훑으며(scan) 그 어노테이션이 붙은 클래스를 찾아서 빈으로 등록하는데, 이 과정을 컴포넌트 스캔(Component Scan)이라 해요.
@SpringBootApplication이 붙은 메인 클래스가 컴포넌트 스캔의 출발점이에요. 이 어노테이션 안에 스캔 기능이 포함돼 있어서, 메인 클래스가 있는 패키지와 그 하위 패키지를 자동으로 훑어요.
@SpringBootApplication // 이 위치를 기준으로 하위 패키지를 스캔
public class StudyApplication {
public static void main(String[] args) {
SpringApplication.run(StudyApplication.class, args);
}
}
그래서 보통 메인 클래스를 최상위 패키지에 두고, 나머지 컨트롤러·서비스·리포지토리를 그 하위에 배치해요. (@SpringBootApplication은 CH 05에서 자세히!)
빈을 등록했으면, 이제 서로 연결(주입)해야죠. 이때 쓰는 게 @Autowired예요. 필요한 빈을 스프링이 컨테이너에서 찾아 자동으로 넣어줘요. (CH 02에서 배운 DI가 실제 코드로 나타난 거예요.)
new로 만들어 넣지 않아도, 스프링이 컨테이너에 등록된 빈 중 딱 맞는 것을 찾아 끼워줘요. 가구 조립 설명서에 "여기엔 A부품을 꽂으세요"라고 적어두면 조립 담당자가 창고에서 A부품을 찾아 대신 꽂아주는 것과 같아요.@Autowirednew 안 해요MemberService는 MemberRepository가 필요해요. @Autowired를 붙이면 스프링이 등록된 MemberRepository 빈을 찾아 자동으로 주입해줘요. 직접 new 하지 않아요.
@Service
public class MemberService {
private final MemberRepository memberRepository;
@Autowired // MemberRepository 빈을 자동 주입
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
💡 생성자가 하나뿐이면 @Autowired를 생략해도 스프링이 자동으로 주입해줘요. 요즘은 생성자 주입을 권장해요.
컴포넌트 스캔은 내가 만든 클래스를 자동 등록하는 데 좋아요. 그런데 외부 라이브러리 객체처럼 내가 어노테이션을 붙일 수 없는 경우엔 어떻게 할까요? 이럴 때 자바 설정 클래스(XML 대신 자바 코드로 빈을 등록하는 설정 클래스예요)로 직접(수동) 등록해요.
@Component를 붙일 수가 없어요(파일을 고칠 수 없으니까요). 그래서 내 설정 클래스에서 @Bean 메서드로 "이 객체를 빈으로 등록해줘"라고 대신 등록해주는 거예요.@Configuration을 붙이면 설정 클래스가 돼요. 그 안의 메서드에 @Bean을 붙이면, 그 메서드가 반환하는 객체가 빈으로 등록돼요. 개발자가 직접 생성 로직을 작성하므로 세밀한 제어가 가능해요.
@Configuration // 설정 클래스
public class AppConfig {
@Bean // 반환 객체를 빈으로 등록
public MemberRepository memberRepository() {
return new MemberRepository();
}
@Bean
public MemberService memberService() {
// 위의 memberRepository() 빈을 주입해 등록
return new MemberService(memberRepository());
}
}
💡 정리하면 — 내 클래스는 @Component 계열 + 컴포넌트 스캔으로 자동, 외부 객체나 세밀한 설정은 @Configuration + @Bean으로 수동 등록해요.
@Bean은 메서드에 붙고, @Component는 클래스에 붙어요. 둘 다 결과적으로 빈을 등록하지만, 붙는 위치와 등록 방식(수동/자동)이 달라요.@Configuration 클래스처럼 자바 코드로 적어요. 자바로 적으면 오타가 나도 컴파일 단계에서 바로 걸리고, IDE의 자동완성 도움도 받을 수 있어 더 안전하고 편해요. (똑같은 "빈 등록"을 XML로 쓰느냐 자바로 쓰느냐의 차이일 뿐이에요.)