
스프링에서 @Component, @Service, @Repository, @Controller는 모두 스테레오 타입(Stereotype) 어노테이션이라고 불린다. 이들은 스프링 컨테이너가 어떤 클래스를 빈(Bean)으로 등록할지 알려주는 표식 역할을 한다.
공통점: 빈 스캐닝 (Component Scanning)
이 어노테이션들이 붙은 클래스는 스프링이 기동 될 때 자동으로 찾아내서(Scanning) IoC 컨테이너에 객체(Bean)를 등록한다.
@Service, @Repository, @Controller 이 어노테이션들은 @Component에게 상속(메타 어노테이션)을 받고 있다.
각 어노테이션의 역할과 차이점
스프링은 계층별 아키텍처(Layered Architecture)를 권장하기 때문에, 각 계층의 목적에 맞게 어노테이션을 나누어 사용한다.
| 어노테이션 | 위치(Layer) | 주요 역할 및 특징 |
| @Component | 공통 / 기타 | 가장 기본이 되는 어노테이션 특정 계층에 속하지 않는 유틸리티, 헬퍼 클래스 등에 사용된다. |
| @Controller | Presentation | 웹 요청(HTTP Reqeust)를 받고 응답을 반환하는 컨트롤러임을 명시한다. Spring MVC에서 핸들러 매핑이 이를 인식한다. |
| @Service | Business | 비즈니스 로직이 수행되는 곳임을 나타낸다. 특별한 기술적 기능은 없지만, 여기에 핵심 로직이 있다는 가독성을 높여준다. |
| @Repository | Persistence | DB 접근 로직(DAO)이 있는 클래스에 사용한다. 가장 큰 특징은 DB 예외를 스프링의 공통 예외(DataAccessException)로 변환해주는 것이다. |
각 어노테이션 예시
@Controller (또는 @RestController)
외부의 요청을 가장 먼저 받는 프레젠테이션 계층
@RestController // @Controller + @ResponseBody (JSON 응답에 최적화)
@RequestMapping("/members")
public class MemberController {
private final MemberService memberService;
// 생성자 주입 (DI)
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
@PostMapping("/join")
public String join(@RequestBody MemberDto memberDto) {
memberService.register(memberDto);
return "회원가입 성공!";
}
}
@Service
핵심 로직이 들어가는 비즈니스 계층
@Service
public class MemberService {
private final MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
public void register(MemberDto dto) {
// 1. 중복 회원 검증 같은 비즈니스 로직 수행
validateDuplicateMember(dto.getEmail());
// 2. DB 저장을 위해 Repository 호출
memberRepository.save(dto.toEntity());
}
private void validateDuplicateMember(String email) {
// 중복 검증 로직...
}
}
@Repository
데이터베이스에 접근하는 데이터 접근 계층(Persistence Layer)
@Repository
public class MemberRepository {
// 실제로는 EntityManager나 JdbcTemplate 등을 사용함
public void save(MemberEntity member) {
// DB에 저장하는 쿼리 실행
// 여기서 발생하는 DB 에러는 스프링의 DataAccessException으로 변환됨
System.out.println("DB에 회원 저장 완료: " + member.getName());
}
}
@Component
위의 세 계층에 속하지 않지만, 스프링 빈으로 관리되어야 하는 일반적인 유틸리티나 헬퍼 클래스
@Component
public class PasswordEncoder {
public String encode(String rawPassword) {
// 비밀번호를 암호화하는 공통 유틸리티 로직
return "encrypted-" + rawPassword;
}
}
용어 정리
핸들러 매핑 (Handler Mapping): 어떤 요청을 어떤 컨트롤러가 처리할지 결정하는 지도
- 역할: 웹 브라우저로부터 HTTP 요청(예: GET /users/1)이 들어왔을 때, 스프링의 문지기인 DispatcherServlet은 이 요청을 누가 처리해야 할지 모릅니다. 이때 핸들러 매핑에게 "이 URL은 누가 담당하니?"라고 물어보고, 그 요청을 처리할 컨트롤러(핸들러)를 찾아오는 역할을 한다.
- 작동 방식: 우리가 클래스에 @Controller를 붙이고 메서드에 @RequestMapping이나 @GetMapping을 사용하는 것이 바로 핸들러 매핑에 "이 URL은 내가 처리할게!"라고 등록하는 과정이다.
- 비유: 백화점 안내 데스크(Handler Mapping)가 손님이 찾는 매장(Controller)의 위치를 알려주는 것과 같다.
DataAccessException: 데이터베이스 오류를 스프링 방식의 언어로 번역한 예외
- 배경: DB마다 오류 코드가 다르다. MySQL에서 발생하는 오류와 Oracle에서 발생하는 오류가 서로 다른 예외(Exception)를 던지면, 개발자는 DB를 바꿀 때마다 예외 처리 코드도 다 고쳐야 한다.
- 역할: 스프링은 DB 종류에 상관없이 일관된 예외 처리를 할 수 있도록 각 DB의 예외를 잡아 스프링만의 공통 예외인 DataAccessException으로 변환해 준다.
- 특징:
- 런타임 예외(Unchecked Exception): 일일이 try-catch로 잡지 않아도 되므로 코드가 깔끔해진다.
- 예외 추상화: @Repository 어노테이션의 핵심 기능 중 하나가 바로 해당 계층에서 발생하는 기술적인 에러(SQLException 등)를 이 DataAccessException으로 변환해 주는 것이다.
- 비유: 전 세계의 다양한 언어(각 DB의 예외)를 공용어인 영어(DataAccessException)로 통역해 주는 것과 같다.
왜 @Component로 사용하지 않고 나눠서 사용할까?
모두 똑같이 빈으로 등록되는데 굳이 나누어 쓰는 이유는 크게 3가지이다.
- 가독성과 의미 전달: 코드를 딱 봤을 때 "아, 이 클래스는 비즈니스 로직을 담당하는구나"라고 바로 파악할 수 있다.
- AOP(관점 지향 프로그래밍) 적용: 특정 계층에만 공통 기능을 적용하고 싶을 때(예: 모든 Service 계층에 로그 남기기) 포인트컷 설정을 쉽게 할 수 있다.
- 계층별 특수 기능 부여: * @Repository는 DB 예외 변환 기능을 제공한다.
- @Controller는 스프링 MVC에서 컨트롤러로 인식되어 매핑 규칙에 포함됩니다.
면접 답변식 요약
모두 스프링 컨테이너가 관리하는 빈(Bean) 임을 나타내는 표식입니다. 근본적으로는 모두 @Component를 포함하고 있어 컴포넌트 스캔의 대상이 된다는 공통점이 있습니다.
차이점은 애플리케이션 계층 구조에 따른 역할 분담에 있습니다.
@Controller는 웹 요청과 응답을 처리하는 프레젠테이션 계층에서 사용되며, @Service는 핵심 비즈니스 로직이 담긴 서비스 계층을 명시합니다.
@Repository는 데이터 접근 계층에서 사용되며, 특히 데이터베이스 예외를 스프링의 예외 추상화 계층으로 변환해 주는 기술적 특징이 있습니다.
마지막으로 @Component는 이러한 계층에 속하지 않는 일반적인 빈을 등록할 때 사용합니다.
이렇게 나누어 사용하는 이유는 코드의 가독성을 높이고, AOP 등을 활용해 계층별로 공통 기능을 유연하게 적용하기 위함입니다.
'Daily Dev Q&A 정리 템플릿' 카테고리의 다른 글
| 25.12.30 @SpringBootApplication 어노테이션이 꼭 필요한가요? (0) | 2025.12.30 |
|---|---|
| 25.12.29 spring의 예외처리방식 (0) | 2025.12.29 |
| 25.12.26 Spring Bean은 무엇인가요? (0) | 2025.12.26 |
| 25.12.24 스프링 부트은 스프링과 다른 건가요? (0) | 2025.12.24 |
| 25.12.23 IoC와 DI는 스프링에서 어떤 역할을 할까? (0) | 2025.12.23 |