Spring_17) 어노테이션 없는 스프링은 팥 없는 단팥빵
2025. 12. 26. 12:15

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) {