Topic (오늘의 주제)
스프링에서 사용하는 주요 어노테이션(@Component, @Service, @Repository, @Controller)의 역할과 차이점을 이해하고, 각 어노테이션을 언제 사용해야 하는지 학습한다.
Why (왜 사용하는가? 왜 중요한가?)
- 어노테이션 없이는 모든 클래스를 XML이나 Java Config로 직접 등록해야 하며, 설정 파일이 복잡해지고 유지보수가 어려워집니다. 또한 각 계층의 역할을 명확히 구분하기 어렵습니다.
- 스프링 어노테이션을 사용하면 클래스에 어노테이션만 붙이면 자동으로 스프링 빈으로 등록되어 설정이 간소화됩니다. 각 어노테이션은 계층별 역할을 명확히 구분하여 코드의 가독성과 유지보수성을 향상시킵니다.
@Component,@Service,@Repository,@Controller의 차이점과 각각의 특별한 기능, 그리고 언제 어떤 어노테이션을 사용해야 하는지 이해해야 합니다.
Core Concept (핵심 개념 정리)
스프링 어노테이션은 클래스나 메서드에 메타데이터를 추가하여 스프링 컨테이너가 자동으로 처리할 수 있게 해주는 표식입니다.
컴포넌트 스캔의 기본:
@Component는 모든 스프링 빈 등록 어노테이션의 부모입니다@Service,@Repository,@Controller는 모두@Component를 포함합니다- 기능적으로는 동일하지만, 의미적 구분을 위해 계층별로 다른 어노테이션을 사용합니다
[!tip] 핵심 포인트
모든 어노테이션은 기능적으로 동일하게 스프링 빈으로 등록되지만, 각 어노테이션은 계층별 역할을 명확히 구분하여 코드의 가독성과 유지보수성을 향상시킵니다.
1. @Component
@Component란?
@Component는 스프링이 자동으로 스프링 빈으로 등록하는 가장 기본적인 어노테이션입니다.
특징
- 범용 어노테이션: 특정 계층에 속하지 않는 일반적인 컴포넌트에 사용
- 컴포넌트 스캔 대상:
@ComponentScan으로 자동 스캔되어 빈으로 등록 - 기본 기능: 스프링 빈 등록만 수행 (추가 기능 없음)
사용 예시
@Component
public class UserValidator {
public boolean validate(User user) {
// 검증 로직
return user != null && user.getName() != null;
}
}
언제 사용하는가?
- 특정 계층에 속하지 않는 유틸리티 클래스
- 공통 기능을 제공하는 헬퍼 클래스
- 범용 컴포넌트
2. @Service
@Service란?
@Service는 비즈니스 로직을 담당하는 서비스 계층에 사용하는 어노테이션입니다.
특징
- 의미적 구분: 비즈니스 로직 계층임을 명확히 표현
- 기능적으로는 @Component와 동일: 내부적으로
@Component를 포함 - 트랜잭션 관리: 일반적으로 서비스 계층에서 트랜잭션을 관리
내부 구조
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // @Component를 포함
public @interface Service {
@AliasFor(annotation = Component.class)
String value() default "";
}
사용 예시
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Transactional
public User createUser(String name, String email) {
// 비즈니스 로직
User user = new User(name, email);
return userRepository.save(user);
}
}
언제 사용하는가?
- 비즈니스 로직을 처리하는 서비스 클래스
- 트랜잭션을 관리하는 계층
- 여러 Repository를 조합하여 복잡한 비즈니스 로직을 처리하는 클래스
3. @Repository
@Repository란?
@Repository는 데이터 접근 계층(DAO, Data Access Object)에 사용하는 어노테이션입니다.
특징
- 의미적 구분: 데이터 접근 계층임을 명확히 표현
- 기능적으로는 @Component와 동일: 내부적으로
@Component를 포함 - 예외 변환: JPA의
PersistenceException을 스프링의DataAccessException으로 자동 변환
내부 구조
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // @Component를 포함
public @interface Repository {
@AliasFor(annotation = Component.class)
String value() default "";
}
예외 변환 기능
@Repository가 붙은 클래스는 AOP 프록시가 생성되어 예외를 자동으로 변환합니다.
예시:
@Repository
public class UserRepository {
public User findById(Long id) {
// JPA 예외 발생 시
// PersistenceException → DataAccessException으로 자동 변환
return entityManager.find(User.class, id);
}
}
예외 변환의 장점:
- JPA, JDBC, MyBatis 등 다양한 데이터 접근 기술을 써도 모든 예외가
DataAccessException으로 통일됨 - 예외 처리를 일관되게 할 수 있음
@Transactional과 함께 사용할 때 트랜잭션 롤백도 안전하게 작동
사용 예시
@Repository
public class UserRepository {
@PersistenceContext
private EntityManager em;
public User save(User user) {
em.persist(user);
return user;
}
public User findById(Long id) {
return em.find(User.class, id);
}
}
언제 사용하는가?
- 데이터베이스 접근을 담당하는 Repository 클래스
- JPA, MyBatis 등을 사용하는 데이터 접근 계층
- 예외 변환이 필요한 데이터 접근 클래스
4. @Controller
@Controller란?
@Controller는 웹 계층(프레젠테이션 계층)의 컨트롤러에 사용하는 어노테이션입니다.
특징
- 의미적 구분: 웹 계층의 컨트롤러임을 명확히 표현
- 기능적으로는 @Component와 동일: 내부적으로
@Component를 포함 - 핸들러 매핑: 스프링 MVC에서 요청을 처리하는 핸들러로 인식
내부 구조
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // @Component를 포함
public @interface Controller {
@AliasFor(annotation = Component.class)
String value() default "";
}
사용 예시
@Controller
@RequestMapping("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public String getUser(@PathVariable Long id, Model model) {
User user = userService.findById(id);
model.addAttribute("user", user);
return "user/detail";
}
@PostMapping
public String createUser(@ModelAttribute User user) {
userService.createUser(user);
return "redirect:/users";
}
}
@RestController와의 차이
@RestController는 @Controller + @ResponseBody를 합친 어노테이션입니다.
// @Controller: 뷰를 반환
@Controller
public class UserController {
@GetMapping("/users")
public String getUsers(Model model) {
// 뷰 이름 반환
return "users/list";
}
}
// @RestController: JSON/XML 등 데이터를 반환
@RestController
public class UserRestController {
@GetMapping("/api/users")
public List<User> getUsers() {
// JSON 데이터 반환
return userService.findAll();
}
}
언제 사용하는가?
- 웹 요청을 처리하는 컨트롤러 클래스
- 뷰를 반환하는 MVC 컨트롤러
- RESTful API를 제공하는 경우
@RestController사용
5. 어노테이션 간의 관계
상속 관계
모든 어노테이션은 내부적으로 @Component를 포함합니다.
@Component (부모)
├─ @Service
├─ @Repository
└─ @Controller
기능적 동일성
모든 어노테이션은 기능적으로 동일하게 스프링 빈으로 등록됩니다.
// 모두 동일하게 스프링 빈으로 등록됨
@Component
public class ComponentClass { }
@Service
public class ServiceClass { }
@Repository
public class RepositoryClass { }
@Controller
public class ControllerClass { }
의미적 차이
기능은 동일하지만, 각 어노테이션은 계층별 역할을 명확히 구분합니다.
| 어노테이션 | 계층 | 역할 | 특별한 기능 |
|---|---|---|---|
@Component |
범용 | 일반 컴포넌트 | 없음 |
@Service |
서비스 계층 | 비즈니스 로직 | 없음 (의미적 구분) |
@Repository |
데이터 접근 계층 | 데이터 접근 | 예외 변환 |
@Controller |
웹 계층 | 요청 처리 | 핸들러 매핑 |
6. 어노테이션 비교표
기능 비교
| 항목 | @Component | @Service | @Repository | @Controller |
|---|---|---|---|---|
| 스프링 빈 등록 | ✅ | ✅ | ✅ | ✅ |
| 컴포넌트 스캔 대상 | ✅ | ✅ | ✅ | ✅ |
| 예외 변환 | ❌ | ❌ | ✅ | ❌ |
| 핸들러 매핑 | ❌ | ❌ | ❌ | ✅ |
| 의미적 구분 | 범용 | 서비스 계층 | 데이터 접근 계층 | 웹 계층 |
사용 시나리오 비교
| 어노테이션 | 사용 시나리오 | 예시 |
|---|---|---|
@Component |
범용 컴포넌트, 유틸리티 클래스 | UserValidator, EmailSender |
@Service |
비즈니스 로직 처리 | UserService, OrderService |
@Repository |
데이터베이스 접근 | UserRepository, OrderRepository |
@Controller |
웹 요청 처리 | UserController, OrderController |
7. 실제 사용 예시
전체 구조 예시
// 1. Repository 계층
@Repository
public class UserRepository {
@PersistenceContext
private EntityManager em;
public User findById(Long id) {
return em.find(User.class, id);
}
}
// 2. Service 계층
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Transactional
public User findUser(Long id) {
return userRepository.findById(id);
}
}
// 3. Controller 계층
@Controller
@RequestMapping("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {'Spring' 카테고리의 다른 글
| Spring_19) 내가 작성하지 않은 어노테이션, 함부로 지우면 안 되는 이유 (0) | 2025.12.30 |
|---|---|
| Spring_18) 에러가 아닙니다. 예외입니다. (1) | 2025.12.30 |
| Spring_16) New 와 Bean는 어색한 사이 (0) | 2025.12.26 |
| Spring_15) Spring boot: 내장 Tom(Cat) and Junior (0) | 2025.12.26 |
| Spring_14) IoC 모르고 DI를 이해 못 한 채 @Autowired를 누르다 (0) | 2025.12.23 |