[MVC-4] 스프링 MVC 시작하기: 애노테이션 기반 컨트롤러

2025. 12. 30. 18:54·Spring/MVC

1. 스프링 MVC의 시작

1.1 @RequestMapping 애노테이션의 등장 배경

 스프링 MVC의 진정한 강점은 애노테이션 기반 컨트롤러의 도입으로 나타났다. 초기 자바 언어에는 애노테이션이 존재하지 않았으며, 스프링 또한 처음부터 이러한 유연한 컨트롤러를 제공하지 않았다. 역사적으로 스프링 프레임워크의 MVC 부분은 상대적으로 약점으로 지적받아 왔고, 많은 개발자들이 Struts와 같은 다른 웹 프레임워크를 MVC 레이어로 사용하기도 했다.

 

 그러나 @RequestMapping 애노테이션 기반 컨트롤러의 등장은 게임 체인저 역할을 했다. 이 기술은 스프링이 웹 MVC 분야에서 압도적인 우위를 차지하는 계기가 되었으며, 오늘날 스프링 MVC는 자바 웹 개발의 사실상 표준(de facto standard)으로 자리잡았다.

1.2 핵심 컴포넌트: RequestMappingHandlerMapping과 RequestMappingHandlerAdapter

애노테이션 기반 컨트롤러의 동작을 뒷받침하는 핵심 컴포넌트들은 다음과 같다:

  • RequestMappingHandlerMapping: @RequestMapping 또는 @Controller 애노테이션이 적용된 클래스를 핸들러로 매핑
  • RequestMappingHandlerAdapter: 애노테이션 기반 컨트롤러를 실행할 수 있는 어댑터

이 두 컴포넌트는 스프링 MVC에서 가장 높은 우선순위를 가지며, 실무에서는 99.9% 이상의 경우 이 방식을 사용한다.


2. 기본적인 @RequestMapping 컨트롤러 구현

2.1 회원 등록 폼 컨트롤러

@Controller
public class SpringMemberFormControllerV1 {

    @RequestMapping("/springmvc/v1/members/new-form")
    public ModelAndView process() {
        return new ModelAndView("new-form");
    }
}

2.1.1 @Controller 애노테이션의 역할

  • 스프링 빈 자동 등록: 내부적으로 @Component 애노테이션을 포함하고 있어 컴포넌트 스캔 대상이 됨
  • 컨트롤러 인식: 스프링 MVC가 애노테이션 기반 컨트롤러로 인식하는 마커 역할 수행

2.1.2 @RequestMapping 애노테이션의 역할

  • URL 매핑: 지정된 URL 패턴에 대한 요청을 해당 메서드에 매핑
  • 메서드 이름 자유: 애노테이션 기반으로 동작하므로 메서드 이름은 개발자가 임의로 지정 가능

2.2 다양한 컨트롤러 등록 방식

2.2.1 @Component와 @RequestMapping 조합

@Component  // 컴포넌트 스캔을 통해 스프링 빈으로 등록
@RequestMapping
public class SpringMemberFormControllerV1 {

    @RequestMapping("/springmvc/v1/members/new-form")
    public ModelAndView process() {
        return new ModelAndView("new-form");
    }
}

2.2.2 스프링 빈 직접 등록 방식

@RequestMapping
public class SpringMemberFormControllerV1 {

    @RequestMapping("/springmvc/v1/members/new-form")
    public ModelAndView process() {
        return new ModelAndView("new-form");
    }
}

// ServletApplication 클래스 내부
@Bean
TestController testController() {
    return new TestController();
}

2.3 회원 저장 컨트롤러

@Controller
public class SpringMemberSaveControllerV1 {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @RequestMapping("/springmvc/v1/members/save")
    public ModelAndView process(HttpServletRequest request, HttpServletResponse response) {
        String username = request.getParameter("username");
        int age = Integer.parseInt(request.getParameter("age"));

        Member member = new Member(username, age);
        memberRepository.save(member);

        ModelAndView mv = new ModelAndView("save-result");
        mv.addObject("member", member);  // 모델 데이터 추가
        return mv;
    }
}

2.3.1 ModelAndView의 addObject() 메서드

  • 모델 데이터를 뷰에 전달하기 위한 메서드
  • 내부적으로 ModelMap에 데이터를 저장
  • 뷰 렌더링 시 request.setAttribute()를 통해 JSP에 전달됨

2.4 회원 목록 컨트롤러

@Controller
public class SpringMemberListControllerV1 {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @RequestMapping("/springmvc/v1/members")
    public ModelAndView process() {
        List<Member> members = memberRepository.findAll();
        ModelAndView mv = new ModelAndView("members");
        mv.addObject("members", members);
        return mv;
    }
}

3. 컨트롤러 통합

3.1 클래스 레벨 @RequestMapping의 도입

초기 구현에서는 각 컨트롤러마다 중복된 URL 패턴이 존재했다. 이를 개선하기 위해 클래스 레벨에 @RequestMapping을 적용하는 방식이 도입되었다.

@Controller
@RequestMapping("/springmvc/v2/members")
public class SpringMemberControllerV2 {
    // 모든 메서드는 /springmvc/v2/members를 기본 경로로 가짐
}

3.2 통합 컨트롤러 구현

/**
 * 클래스 단위 -> 메서드 단위
 * @RequestMapping 클래스 레벨과 메서드 레벨 조합
 */
@Controller
@RequestMapping("/springmvc/v2/members")
public class SpringMemberControllerV2 {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @RequestMapping("/new-form")
    public ModelAndView newForm() {
        return new ModelAndView("new-form");
    }

    @RequestMapping("/save")
    public ModelAndView save(HttpServletRequest request, HttpServletResponse response) {
        String username = request.getParameter("username");
        int age = Integer.parseInt(request.getParameter("age"));

        Member member = new Member(username, age);
        memberRepository.save(member);

        ModelAndView mav = new ModelAndView("save-result");
        mav.addObject("member", member);
        return mav;
    }

    @RequestMapping  // 클래스 레벨 경로만 적용
    public ModelAndView members() {
        List<Member> members = memberRepository.findAll();
        ModelAndView mav = new ModelAndView("members");
        mav.addObject("members", members);
        return mav;
    }
}

3.3 URL 조합 규칙

클래스 레벨과 메서드 레벨의 @RequestMapping이 조합되는 방식:

클래스 레벨  메서드 레벨  최종 URL
/springmvc/v2/members /new-form /springmvc/v2/members/new-form
/springmvc/v2/members /save /springmvc/v2/members/save
/springmvc/v2/members (없음) /springmvc/v2/members

4. 실용적인 스프링 MVC 방식

4.1 v3: 현대적인 스프링 MVC 컨트롤러

/**
 * v3
 * - Model 도입
 * - ViewName 직접 반환
 * - @RequestParam 사용
 * - @RequestMapping -> @GetMapping, @PostMapping
 */
@Controller
@RequestMapping("/springmvc/v3/members")
public class SpringMemberControllerV3 {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @GetMapping("/new-form")
    public String newForm() {
        return "new-form";  // 뷰 이름 직접 반환
    }

    @PostMapping("/save")
    public String save(
            @RequestParam("username") String username,
            @RequestParam("age") int age,
            Model model) {

        Member member = new Member(username, age);
        memberRepository.save(member);

        model.addAttribute("member", member);  // Model 파라미터 사용
        return "save-result";
    }

    @GetMapping
    public String members(Model model) {
        List<Member> members = memberRepository.findAll();
        model.addAttribute("members", members);
        return "members";
    }
}

4.2 주요 개선 사항

4.2.1 Model 파라미터 자동 주입

  • 스프링 MVC가 요청 처리 시 자동으로 Model 객체를 생성하여 컨트롤러 메서드에 주입
  • ModelAndView 객체를 직접 생성하지 않아도 됨
  • model.addAttribute()를 통해 데이터 추가

4.2.2 뷰 이름 직접 반환

  • ModelAndView 객체 대신 문자열로 뷰 이름 직접 반환 가능
  • 내부적으로 ModelAndView로 변환되어 처리됨

4.2.3 @RequestParam을 통한 파라미터 바인딩

  • @RequestParam("username")은 request.getParameter("username")과 동일한 기능
  • 타입 변환 자동 처리 (String → int 등)
  • GET 쿼리 파라미터와 POST Form 데이터 모두 지원

4.2.4 HTTP 메서드별 매핑 애노테이션

  1. @RequestMapping: 기본적인 URL 매핑
  2. @GetMapping: HTTP GET 요청 매핑
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @RequestMapping(method = RequestMethod.GET)  // 내부 구현
    public @interface GetMapping { ... }
    
  3. @PostMapping: HTTP POST 요청 매핑
  4. @PutMapping: HTTP PUT 요청 매핑
  5. @DeleteMapping: HTTP DELETE 요청 매핑
  6. @PatchMapping: HTTP PATCH 요청 매핑

4.3 HTTP 메서드 구분의 중요성

4.3.1 @RequestMapping의 method 속성

@RequestMapping(value = "/new-form", method = RequestMethod.GET)
public String newForm() {
    return "new-form";
}

4.3.2 전용 애노테이션의 장점

  • 코드 가독성 향상
  • 의도 명확화
  • 오류 감소 (잘못된 메서드로의 요청 방지)

5. 스프링 MVC의 진화 과정 정리

5.1 버전별 주요 특징

버전 특징 장점
v1 기본적인 @RequestMapping 컨트롤러 애노테이션 기반 시작
v2 클래스 레벨 @RequestMapping 도입 중복 URL 제거, 코드 통합
v3 현대적인 실용적 방식 간결한 코드, 강력한 기능

5.2 스프링 MVC의 설계 철학

5.2.1 관심사 분리(Separation of Concerns)

  • URL 매핑과 비즈니스 로직 분리
  • 뷰 렌더링과 컨트롤러 로직 분리
  • HTTP 요청 처리와 응답 생성 분리

5.2.2 관례 우선 구성(Convention over Configuration)

  • 기본값과 관례를 통한 설정 최소화
  • 애노테이션을 통한 선언적 프로그래밍
  • 자동화된 컴포넌트 스캔과 빈 등록

5.2.3 확장성과 유연성

  • 인터페이스 기반 설계로 확장 용이
  • 다양한 뷰 기술 통합 지원
  • 커스텀 핸들러와 어댑터 구현 가능

5.3 실무 적용 시 고려사항

5.3.1 컨트롤러 설계 원칙

  1. 단일 책임 원칙: 하나의 컨트롤러는 하나의 도메인 관련 기능만 처리
  2. RESTful 설계: 자원 기반 URL 구조 설계
  3. 응집도 높이기: 관련 기능들은 하나의 컨트롤러로 그룹화

5.3.2 성능과 보안

  • 적절한 HTTP 메서드 사용 (GET, POST 구분)
  • 파라미터 검증과 예외 처리
  • 세션과 인증 관리

5.3.3 테스트 용이성

  • 서블릿 API 종속성 최소화
  • 단위 테스트와 통합 테스트 지원
  • MockMvc를 통한 컨트롤러 테스트

6. 결론

 스프링 MVC의 진화 과정은 웹 애플리케이션 개발의 복잡성을 효과적으로 관리하기 위한 지속적인 노력의 결과물이다. 애노테이션 기반 컨트롤러의 도입은 단순한 편의 기능을 넘어, 개발자 생산성과 코드 품질을 혁신적으로 향상시켰다.

 

 오늘날의 스프링 MVC는 단순한 웹 프레임워크를 넘어, 엔터프라이즈 수준의 웹 애플리케이션을 구축하기 위한 포괄적인 플랫폼으로 발전했다. 이러한 발전 과정을 이해하는 것은 스프링 MVC를 효과적으로 활용하고, 필요에 따라 확장하는 데 필수적인 기반이 된다.

 

 실제 프로젝트에서는 v3 방식이 표준으로 사용되며, 이는 MVC 프레임워크 만들기에서 경험한 v4의 실용적인 접근 방식을 스프링이 체계적으로 구현한 결과물이다. 이러한 설계 철학과 구현 방식은 스프링이 자바 생태계에서 지속적으로 영향력을 유지하는 데 기여하고 있다.

'Spring > MVC' 카테고리의 다른 글

[MVC-6] CSR 환경에서 @RequestBody만 쓸까? (feat. 파일 업로드와 @ModelAttribute)  (0) 2026.02.18
[MVC-5] 스프링 MVC 기본 기능  (0) 2025.12.30
[MVC-3] Spring MVC 구조 이해  (0) 2025.12.30
[MVC-2] 직접 만드는 MVC 프레임워크  (0) 2025.12.30
[MVC-1] 서블릿에서 JSP까지: 자바 백엔드 웹 기술의 발전과 한계  (0) 2025.12.30
'Spring/MVC' 카테고리의 다른 글
  • [MVC-6] CSR 환경에서 @RequestBody만 쓸까? (feat. 파일 업로드와 @ModelAttribute)
  • [MVC-5] 스프링 MVC 기본 기능
  • [MVC-3] Spring MVC 구조 이해
  • [MVC-2] 직접 만드는 MVC 프레임워크
h6bro
h6bro
백엔드 개발자의 기술 블로그
  • h6bro
    Jun's Tech Blog
    h6bro
  • 전체
    오늘
    어제
    • 분류 전체보기 (250) N
      • Java (18)
        • Core (9)
        • Design Pattern (9)
      • Spring (80)
        • Core (24)
        • MVC (6)
        • DB (10)
        • JPA (26)
        • Monitoring (3)
        • Security (11)
        • WebSocket (0)
      • Database (33)
        • Redis (15)
        • MySQL (18)
      • MSA (25) N
        • MSA 기본 (11)
        • MSA 아키텍처 (14) N
      • Kafka (30)
        • Core (18)
        • Connect (12)
      • ElasticSearch (11)
        • Search (11)
        • Logging (0)
      • Test (4)
        • k6 (4)
      • Docker (9)
      • CI&CD (10)
        • GitHub Actions (6)
        • ArgoCD (4)
      • Kubernetes (18)
        • Core (12)
        • Ops (6)
      • Cloud Engineering (4)
        • AWS Infrastructure (3)
        • AWS EKS (1)
        • Terraform (0)
      • Project (8)
        • LinkFolio (1)
        • Secondhand Market (7)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

    • Cloud Engineering 포스팅 정리
  • 인기 글

  • 태그

    ㅈ
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
h6bro
[MVC-4] 스프링 MVC 시작하기: 애노테이션 기반 컨트롤러
상단으로

티스토리툴바