본문 바로가기
Spring MVC

[MVC] 스프링 MVC - 구조 이해(3)

by 개발 Blog 2025. 2. 22.

공부 내용을 정리하고 앞으로의 학습에 이해를 돕기 위해 작성합니다.

 

스프링 MVC - 시작하기

 

스프링이 제공하는 컨트롤러는 애노테이션 기반으로 동작하여 매우 유연하고 실용적이다. 과거에는 자바에 애노테이션이 없었고, 스프링도 처음부터 이러한 컨트롤러를 제공하지 않았다.

 

@RequestMapping

스프링은 애노테이션을 활용한 매우 유연하고 실용적인 컨트롤러를 만들었으며, 그 대표적인 것이 @RequestMapping 애노테이션을 사용하는 컨트롤러이다.

과거에는 스프링의 MVC 기능이 약하여 MVC 웹 기술로 스트럿츠 같은 다른 프레임워크를 사용했다. 하지만 @RequestMapping 기반 애노테이션 컨트롤러가 등장하면서 스프링의 MVC 기능이 강력해졌고, 현재는 99.9% 이 방식을 사용한다.

 

@RequestMapping과 관련된 핵심 요소

  • RequestMappingHandlerMapping
  • RequestMappingHandlerAdapter

이 두 가지 요소는 애노테이션 기반 컨트롤러를 지원하는 핸들러 매핑과 어댑터이다.

@RequestMapping 기반의 컨트롤러 적용

SpringMemberFormControllerV1 - 회원 등록 폼

package hello.servlet.web.springmvc.v1;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

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

주요 개념

  • @Controller : 스프링이 자동으로 스프링 빈으로 등록하고, 애노테이션 기반 컨트롤러로 인식한다.
  • @RequestMapping : 특정 URL 요청을 해당 메서드와 매핑한다.
  • ModelAndView : 모델과 뷰 정보를 포함하는 객체이다.
  • RequestMappingHandlerMapping@RequestMapping 또는 @Controller 애노테이션이 붙은 클래스를 매핑 대상으로 인식한다.

@Component를 활용한 등록 (동일한 동작 수행)

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

 

스프링 빈 직접 등록하는 방법

@Bean
SpringMemberFormControllerV1 springMemberFormControllerV1() {
    return new SpringMemberFormControllerV1();
}

 

스프링 3.0 이상에서의 주의사항

스프링 부트 3.0(스프링 프레임워크 6.0)부터는 클래스 레벨에 @RequestMapping만 있어서는 컨트롤러로 인식되지 않는다. 반드시 @Controller 애노테이션이 필요하다. (@RestController@Controller를 포함하고 있으므로 문제없음)@Controller 없는 위의 코드는 프링 컨트롤러로 인식되지 않는다. (RequestMappingHandlerMapping에서 @RequestMapping 이제 인식하

않고, Controller  인식한다.)

 

SpringMemberSaveControllerV1 - 회원 저장

package hello.servlet.web.springmvc.v1;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@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);
        System.out.println("member = " + member);
        memberRepository.save(member);

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

 

ModelAndView.addObject() 활용

mv.addObject("member", member)를 통해 Model 데이터를 추가하면, 뷰에서 이를 활용하여 화면을 렌더링할 수 있다.

 

SpringMemberListControllerV1 - 회원 목록 조회

package hello.servlet.web.springmvc.v1;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import java.util.List;

@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;
    }
}

 

실행 URL

  • 회원 등록 폼: http://localhost:8080/springmvc/v1/members/new-form
  • 회원 저장: http://localhost:8080/springmvc/v1/members/save
  • 회원 목록 조회: http://localhost:8080/springmvc/v1/members

스프링 MVC - 컨트롤러 통합

@RequestMapping을 메서드 단위로 적용할 수 있기 때문에 컨트롤러 클래스를 보다 유연하게 하나로 통합할 수 있다.

 

SpringMemberControllerV2

package hello.servlet.web.springmvc.v2;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;

/**
 * 클래스 단위 -> 메서드 단위
 * @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;
    }
}

 

컨트롤러 통합과 조합

컨트롤러 클래스를 하나로 통합할 수 있을 뿐만 아니라, 클래스 레벨의 @RequestMapping을 활용하면 URL 패턴을 효과적으로 조합할 수 있다.

 

예를 들어, 아래와 같은 중복된 URL 매핑이 있을 경우

@RequestMapping("/springmvc/v2/members/new-form") 
@RequestMapping("/springmvc/v2/members") 
@RequestMapping("/springmvc/v2/members/save")

클래스 레벨에서 @RequestMapping("/springmvc/v2/members")를 선언하면, 개별 메서드 레벨의 @RequestMapping과 조합되어 중복을 제거할 수 있다.

 

조합 결과

클래스 레벨메서드 레벨최종 매핑 URL

클래스 레벨메서드 레벨최종 매핑 URL

클래스 레벨 메서드 레벨 최종 매핑 URL
@RequestMapping("/springmvc/v2/members") @RequestMapping("/new-form") /springmvc/v2/members/new-form
@RequestMapping("/springmvc/v2/members") @RequestMapping("/save") /springmvc/v2/members/save
@RequestMapping("/springmvc/v2/members") @RequestMapping (기본 경로) /springmvc/v2/members

 

실행

스프링 MVC - 실용적인 방식

 

스프링 MVC는 개발자가 보다 편리하게 개발할 수 있도록 다양한 편의 기능을 제공한다.
이전 버전(v3)에서는 ModelView를 직접 생성하여 반환해야 했지만, v4에서는 이를 개선하여 더 실용적인 방식을 도입했다.

실무에서는 지금부터 설명하는 방식을 주로 사용한다.

 

SpringMemberControllerV3

package hello.servlet.web.springmvc.v3;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

/**
 * 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);
        return "save-result";
    }

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

 

주요 개선 사항

1. Model 파라미터 사용

  • save()와 members() 메서드를 보면 Model을 파라미터로 받고 있다.
  • 스프링 MVC는 Model을 통해 데이터를 뷰에 전달하는 기능을 제공한다.
  • 이전 방식에서 ModelAndView 객체를 직접 생성해야 했던 불편함을 해결했다.

2. ViewName 직접 반환

  • 컨트롤러 메서드에서 뷰의 논리 이름을 문자열로 직접 반환할 수 있다.
  • 예를 들어, "new-form"을 반환하면 src/main/resources/templates/new-form.html을 렌더링 한다.

3. @RequestParam 사용

  • HTTP 요청 파라미터를 @RequestParam을 통해 편리하게 받을 수 있다.
  • 예를 들어, @RequestParam("username")은 request.getParameter("username")와 동일한 역할을 한다.
  • GET 쿼리 파라미터와 POST Form 방식 모두 지원한다.

4. @GetMapping, @PostMapping 적용

 

@RequestMapping은 기본적으로 URL 매핑뿐만 아니라 HTTP 메서드(GET, POST 등)도 구분할 수 있다.

 

예전 방식

@RequestMapping(value = "/new-form", method = RequestMethod.GET)

 

개선된 방식

@GetMapping("/new-form")
  • @PostMapping도 동일한 방식으로 사용할 수 있다.
  • 참고로 @PutMapping, @DeleteMapping, @PatchMapping 등도 제공된다.

실행 URL