본문 바로가기
이직&취업/Spring Framework

@Component vs @Service vs @Repository vs @Controller: 스프링 어노테이션 차이

by journeylabs 2025. 4. 10.
728x90
반응형

목차

    🔥 스프링 초보도 헷갈리지 않게! @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를 통해 UserRepositoryUserValidator를 주입받아 사용.
    • @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는 스프링의 강력한 도구로, 계층 구조를 명확히 하고 코드를 체계적으로 관리할 수 있게 합니다. 역할에 맞게 사용하면 유지보수가 쉬운 코드를 작성할 수 있습니다.

     

     

    728x90
    반응형