목차
🔥 스프링 초보도 헷갈리지 않게! @Component, @Service, @Repository, @Controller 차이 완벽 정리
Spring Framework를 처음 접하거나, 실무에서 사용 중인 개발자라면 한 번쯤은 이런 고민을 해봤을 거예요.
“도대체 @Component, @Service, @Repository, @Controller는 뭐가 다른 거야?”
이번 포스팅에서는 이 네 가지 어노테이션의 차이점과 사용 시기, 그리고 실무에서 자주 사용되는 예제를 중심으로 한 번에 정리해보겠습니다.
1. 주요 개념 및 특징: 스프링 어노테이션 이해하기
Spring Framework에서 @Component, @Service, @Repository, @Controller는 모두 빈(Bean)으로 등록하기 위한 어노테이션입니다. 이들은 의존성 주입(Dependency Injection)과 자동 스캔(Component Scan)을 통해 스프링 컨테이너에 관리되지만, 각각의 역할과 사용 목적이 다릅니다.
Bean 등록
어노테이션 | 역할 | Bean 등록 여부 |
@Component | 일반적인 컴포넌트 클래스 | ✅ |
@Service | 비즈니스 로직 처리 클래스 | ✅ |
@Repository | DB 처리 클래스 (DAO) | ✅ |
@Controller | 웹 요청을 처리하는 컨트롤러 | ✅ |
이 네 가지는 모두 Spring의 @Component의 세부 형태이며, 컴포넌트 스캔(Component Scan)을 통해 자동으로 Bean으로 등록됩니다.
Bean으로 등록되는 원리
- 스프링은 @ComponentScan을 통해 지정된 패키지 내에서 어노테이션이 붙은 클래스를 탐색합니다.
- 발견된 클래스는 스프링 컨테이너에 Bean으로 등록되어, 필요할 때 주입(@Autowired)되거나 호출됩니다.
- @Component는 기본 어노테이션이며, 나머지 세 개는 이를 특화한 버전입니다.
2. 각 어노테이션의 역할과 사용 시기
✅ @Component
- 역할: 가장 기본적인 어노테이션으로, 일반적인 스프링 빈을 정의합니다.
- 사용 시기: 특정 역할이 명확하지 않은 클래스(예: 유틸리티, 설정 클래스 등)를 빈으로 등록할 때.
- 특징: 다른 세 어노테이션의 상위 개념.
- ex) 공통 유틸리티 클래스, 설정 클래스 등
@Component
public class PasswordEncoderUtil {
public String encode(String input) {
return Base64.getEncoder().encodeToString(input.getBytes());
}
}
✅ @Service
- 역할: 비즈니스 로직을 처리하는 서비스 계층을 나타냅니다.
- 사용 시기: 도메인 로직, 트랜잭션 관리 등 비즈니스 기능이 포함된 클래스에 사용.
- 특징: 코드 가독성을 높이고, 계층 구조를 명확히 함.
- 의미적으로 "이 클래스는 서비스 역할이다"라는 명확한 구분을 위해 사용
@Service
public class UserService {
public void registerUser(User user) {
// 회원가입 비즈니스 로직 처리
}
}
✅ @Repository
- 역할: 데이터 액세스 계층(DAO, Data Access Object)을 나타냅니다.
- 사용 시기: 데이터베이스와 상호작용하는 클래스(예: CRUD 작업)에 사용.
- 특징: 예외를 스프링의 DataAccessException으로 변환해줍니다.
- Spring이 예외 처리를 더 잘 할 수 있도록 도와줌
- @Repository는 내부적으로 PersistenceExceptionTranslationPostProcessor에 의해 예외를 Spring DataAccessException으로 변환
@Repository
public class UserRepository {
public User findById(Long id) {
// DB에서 사용자 조회
}
}
✅ @Controller
- 역할: 웹 요청을 처리하는 컨트롤러 계층을 나타냅니다.
- 사용 시기: REST API나 MVC 요청 핸들러를 작성할 때.
- 특징: @RequestMapping과 함께 사용되며, HTTP 요청/응답을 관리.
- @RestController와는 다름 (@RestController = @Controller + @ResponseBody)
@Controller
public class UserController {
@GetMapping("/signup")
public String showSignupForm() {
return "signup"; // signup.html
}
}
3. 예시 및 코드 설명
스프링에서 @Component, @Service, @Repository, @Controller의 차이를 이해하려면 실제 코드로 동작 방식을 확인하는 것이 가장 효과적입니다. 여기서는 간단한 사용자 관리 시스템을 예로 들어 각 어노테이션의 역할과 사용법을 자세히 살펴보겠습니다. 코드를 하나씩 분석하며, 왜 특정 어노테이션을 선택했는지, 어떤 상황에서 유용한지도 함께 설명합니다.
전체 코드 구조
먼저, 사용자 데이터를 다루는 간단한 시스템을 설계합니다. 필요한 클래스와 각 어노테이션의 적용은 다음과 같습니다:
- User: 사용자 데이터 모델.
- UserValidator (@Component): 유효성 검사 유틸리티.
- UserRepository (@Repository): 데이터 저장소.
- UserService (@Service): 비즈니스 로직 처리.
- UserController (@Controller): 웹 요청 처리.
1. User 클래스: 데이터 모델
public class User {
private Long id;
private String name;
public User() {} // 기본 생성자 (JSON 직렬화를 위해 필요)
public User(Long id, String name) {
this.id = id;
this.name = name;
}
// Getter와 Setter
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@Override
public String toString() {
return "User{id=" + id + ", name='" + name + "'}";
}
}
- User는 단순한 POJO(Plain Old Java Object)로, 데이터를 저장합니다. 어노테이션이 없어도 스프링 빈으로 등록할 필요는 없지만, 다른 빈에서 활용됩니다.
2. @Component: UserValidator - 유틸리티 클래스
import org.springframework.stereotype.Component;
@Component
public class UserValidator {
public boolean validate(User user) {
// 이름이 null이거나 빈 문자열인지 확인
if (user.getName() == null || user.getName().trim().isEmpty()) {
System.out.println("유효성 검사 실패: 이름이 비어있습니다.");
return false;
}
System.out.println("유효성 검사 성공: " + user.getName());
return true;
}
}
- @Component: 이 클래스를 스프링 빈으로 등록합니다. 특정 계층(서비스, 레포지토리, 컨트롤러)에 속하지 않는 일반적인 기능을 제공할 때 사용합니다.
- 역할: 사용자 데이터의 유효성을 검사하는 로직을 수행합니다.
- 동작: validate() 메서드는 User 객체의 name 필드를 체크하고, 결과를 출력하며 boolean 값을 반환합니다.
- 왜 @Component인가?: 계층 구조에 속하지 않는 독립적인 유틸리티로, 다른 빈에서 의존성 주입으로 사용됩니다.
3. @Repository: UserRepository - 데이터 액세스 계층
import org.springframework.stereotype.Repository;
import java.util.HashMap;
import java.util.Map;
@Repository
public class UserRepository {
private final Map<Long, User> userStore = new HashMap<>();
private Long idCounter = 1L;
public User save(User user) {
if (user.getId() == null) {
user.setId(idCounter++); // 새로운 ID 할당
}
userStore.put(user.getId(), user);
System.out.println("저장된 사용자: " + user);
return user;
}
public User findById(Long id) {
User user = userStore.get(id);
System.out.println("조회된 사용자: " + user);
return user;
}
}
- @Repository: 데이터 액세스 계층을 나타내며, 스프링 빈으로 등록됩니다. 데이터베이스 작업을 모방하기 위해 HashMap을 사용했습니다.
- 역할: 사용자 데이터를 저장하고 조회하는 CRUD 작업을 처리합니다.
- 동작:
- save(): 새로운 사용자를 추가하거나 기존 사용자를 업데이트하며, ID가 없으면 자동으로 생성.
- findById(): ID로 사용자를 조회.
- 특징: 실제 데이터베이스 연동 시 JDBC나 JPA를 사용할 수 있으며, 예외 발생 시 스프링이 DataAccessException으로 변환해줍니다.
- 왜 @Repository인가?: 데이터와의 상호작용을 담당하므로, 이 계층을 명확히 표시하고 예외 처리를 활용하기 위해 사용.
4. @Service: UserService - 비즈니스 로직 계층
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private final UserRepository userRepository;
private final UserValidator userValidator;
@Autowired
public UserService(UserRepository userRepository, UserValidator userValidator) {
this.userRepository = userRepository;
this.userValidator = userValidator;
}
public User createUser(String name) {
User user = new User(null, name);
if (userValidator.validate(user)) {
return userRepository.save(user);
}
throw new IllegalArgumentException("유효하지 않은 사용자 데이터");
}
public User findById(Long id) {
return userRepository.findById(id);
}
}
- @Service: 비즈니스 로직을 처리하는 서비스 계층을 나타내며, 빈으로 등록됩니다.
- 역할: 사용자 생성과 조회를 위한 로직을 조합합니다.
- 동작:
- createUser(): UserValidator로 유효성을 확인 후, UserRepository에 저장.
- findById(): 저장소에서 사용자 조회를 위임.
- 의존성 주입: @Autowired를 통해 UserRepository와 UserValidator를 주입받아 사용.
- 왜 @Service인가?: 비즈니스 로직을 캡슐화하고, 트랜잭션 관리(예: @Transactional)를 추가할 수 있는 계층이기 때문.
5. @Controller: UserController - 웹 요청 처리 계층
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping("/users")
public class UserController {
private final UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@PostMapping
@ResponseBody
public User createUser(@RequestBody String name) {
return userService.createUser(name);
}
@GetMapping("/{id}")
@ResponseBody
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
}
- @Controller: 웹 요청을 처리하는 컨트롤러 계층을 나타내며, 빈으로 등록됩니다. @ResponseBody로 JSON 응답을 반환합니다.
- 역할: 클라이언트의 HTTP 요청을 받아 서비스 계층과 연결.
- 동작:
- createUser(): POST 요청으로 사용자 생성 요청을 받아 처리.
- getUser(): GET 요청으로 특정 사용자 조회.
- 특징: @RequestMapping으로 URL 경로를 매핑하고, @PathVariable과 @RequestBody로 데이터를 추출.
- 왜 @Controller인가?: 웹 애플리케이션의 진입점으로, HTTP 요청과 응답을 관리하기 위해 사용.
코드 분석 요약
- 계층 분리: 각 어노테이션은 책임과 역할을 명확히 구분합니다.
- 의존성 주입: @Autowired로 빈 간 연결을 처리하며, 느슨한 결합을 유지.
- 확장성: 필요 시 데이터베이스 연동(@Repository)이나 트랜잭션 추가(@Service)가 가능.
4. 주의사항 및 장단점
✅ 장점
- 코드의 역할을 명확하게 구분할 수 있음
- 의존성 주입이 간결해짐 (@Autowired 없이 생성자 주입 사용 가능)
- 스프링이 예외를 자동으로 처리하거나 AOP 포인트컷 적용이 쉬움
❌ 단점
- 잘못 사용하면 의미 없는 네이밍만 늘어남 (ex. 모든 클래스에 @Service 사용)
- 계층이 명확하지 않으면 오히려 혼란을 줄 수 있음
- 자동 스캔 범위를 벗어나면 Bean으로 등록되지 않음
✅ 결론: 역할 중심의 어노테이션, 제대로 쓰자
@Component, @Service, @Repository, @Controller는 스프링의 강력한 도구로, 계층 구조를 명확히 하고 코드를 체계적으로 관리할 수 있게 합니다. 역할에 맞게 사용하면 유지보수가 쉬운 코드를 작성할 수 있습니다.
'이직&취업 > Spring Framework' 카테고리의 다른 글
Spring MVC에서 요청 흐름은 어떻게 되나요? (18) | 2025.04.11 |
---|---|
Spring Bean의 라이프사이클은 어떻게 되나요? (18) | 2025.04.10 |
Spring Framework IoC (Inversion of Control)란? (21) | 2025.03.21 |
Spring Framework DI (의존성 주입) 란? (15) | 2025.03.20 |
Spring Framework AOP 란? (13) | 2025.03.20 |