[LG U+ 유레카 3기]Spring MVC | 요청 바인딩 + View/Model/Redirect 실습 정리

2025. 11. 5. 12:19Java/Spring

❶ Spring MVC 내부 동작 구조

Spring MVC는 DispatcherServlet을 중심으로 동작하는 Front Controller 패턴이다.
즉, 모든 요청이 DispatcherServlet을 통과하면서 Controller, View(JSP), Model 객체를 연결한다.

📍 내부 요청 처리 순서

  1. 클라이언트 요청 → Tomcat이 수신 후 DispatcherServlet에 전달
  2. HandlerMapping이 어떤 Controller가 처리할지 탐색
  3. ArgumentResolver가 컨트롤러 파라미터를 분석 후 생성 (@RequestParam, DTO 등)
  4. DataBinder가 DTO, Map, Header 등의 데이터를 자바 객체로 변환
  5. Controller 메서드 실행 → Model 객체에 데이터 담기
  6. ViewResolver가 JSP 파일 경로를 찾아 Forward 또는 Redirect 수행
---

❷ @RequestParam으로 파라미터 받기

✅ 기본 바인딩


@GetMapping("/param3")
public void m3(Integer bookId, String bookName) {
    System.out.println(bookId);
    System.out.println(bookName);
}
요청: GET /param3?bookId=10&bookName=스프링

요청 파라미터 이름과 변수명이 같으면 자동으로 바인딩된다.
단, int는 null 허용이 안 되므로 Integer를 사용하는 것이 안전하다.

✅ 선택 파라미터


@PostMapping("/param4")
public void m4(@RequestParam(required = false) String bookName) {
    System.out.println(bookName);
}
파라미터가 없어도 400 에러가 발생하지 않고 null 값이 들어온다.

✅ 파라미터 이름이 다를 때


@PostMapping("/param5")
public void m5(@RequestParam(name = "bookName2") String bookName) {
    System.out.println(bookName);
}
URL 파라미터 이름이 bookName2이더라도 @RequestParam(name="bookName2") 로 매핑 가능하다.
---

❸ DTO 바인딩 (CarDto)

코드 구조


@PostMapping("/car")
public void m6(CarDto carDto) {
    System.out.println("m6(CarDto carDto)");
    System.out.println(carDto);
}

public class CarDto {
    private String name;
    private Integer price;
    private String owner;

    public CarDto() { System.out.println("CarDto()"); }

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public Integer getPrice() { return price; }
    public void setPrice(Integer price) { this.price = price; }

    public String getOwner() { return owner; }
    public void setOwner(String owner) { this.owner = owner; }

    @Override
    public String toString() {
        return "CarDto [name=" + name + ", price=" + price + ", owner=" + owner + "]";
    }
}

요청 예시

KeyValue
name박진우
price2000
owner지누

📍 내부 동작

  1. DispatcherServlet이 /car 요청을 받음
  2. HandlerMapping이 m6(CarDto) 찾음
  3. ArgumentResolver가 DTO 객체 생성 (기본 생성자 필수)
  4. Setter 기반으로 값 주입 (setName(), setPrice(), setOwner())
  5. 최종적으로 채워진 CarDto 객체를 Controller로 전달

🚨 주의사항

  • 기본 생성자가 없으면 Reflection으로 객체를 만들 수 없어 400 에러 발생
  • int 대신 Integer를 쓰면 null(값 없음)을 표현 가능
  • Spring은 필드명이 아니라 Getter/Setter 이름에서 유추한 프로퍼티명으로 바인딩한다
---

❹ Map으로 전체 파라미터 받기


@PostMapping("/map")
public void m8(@RequestParam Map<String, String> params) {
    System.out.println(params);
}

파라미터 개수가 유동적일 때 유용하다.
Spring이 내부적으로 요청 파라미터를 Map으로 자동 변환한다.

---

❺ Header 값 바인딩 (@RequestHeader)


@GetMapping("/header")
public void m9(
    @RequestHeader("User-Agent") String userAgent,
    @RequestHeader("Accept") String accept,
    @RequestHeader("API-KEY") String apiKey
) {
    System.out.println(userAgent);
}
요청 헤더 예시:

User-Agent: PostmanRuntime/7.49.1
Accept: */*
API-KEY: 123456

🚨 헤더 이름 오타시

MissingRequestHeaderException 발생 → 400 Bad Request
해결: @RequestHeader(required = false) 혹은 defaultValue 지정 ---

❻ View Forwarding (JSP 이동)


@GetMapping("/viewTest1")
public String viewTest1() {
    return "viewTest1";
}

@GetMapping("/viewTest2")
public String viewTest2() {
    return "sub/viewTest2";
}

리턴값이 String일 경우 ViewResolver가 JSP 경로를 완성한다.


spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp
즉, "viewTest1"/WEB-INF/jsp/viewTest1.jsp ---

❼ Model과 함께 데이터 전달


@GetMapping("/viewTest3")
public String viewTest3(Model model) {
    model.addAttribute("seq", "12345");
    model.addAttribute("carDto", new CarDto("myCar", 20000, "홍길동"));
    return "viewTest3";
}

Model에 추가된 데이터는 request scope로 JSP에서 접근 가능하다.


번호: ${seq} 
차 이름: ${carDto.name}
가격: ${carDto.price}
소유자: ${carDto.owner}
---

❽ ModelAndView로 처리


@GetMapping("/viewTest4")
public ModelAndView viewTest4() {
    ModelAndView mav = new ModelAndView();
    mav.addObject("seq", "12345");
    mav.addObject("carDto", new CarDto("myCar", 20000, "홍길동"));
    mav.setViewName("viewTest4");
    return mav;
}

Model 데이터와 View 이름을 동시에 설정할 수 있는 객체.
요즘은 Model + String 조합이 더 많이 쓰인다.

---

❾ Redirect vs Forward


@GetMapping("/redirect")
public String redirect() {
    System.out.println("redirect");
    return "redirect:viewTest1";
}
비교 항목ForwardRedirect
요청 횟수1번2번 (302 + Location)
주소창변경 없음변경됨
데이터 전달Model 유지Model 사라짐
용도내부 JSP 이동다른 URL 재요청
---

❿ 에러 코드 정리

상황코드설명
필수 파라미터 누락, DTO 바인딩 실패400Bad Request
매핑된 Controller 없음404Not Found
JSP 없음404ViewResolver가 JSP 찾지 못함
Redirect 수행302Location 헤더 통해 재요청
---

📘 핵심 용어 정리

DispatcherServlet Spring MVC의 핵심 Front Controller이다. 모든 요청이 DispatcherServlet을 통과하며, 내부적으로 HandlerMapping, HandlerAdapter, ViewResolver에게 역할을 분담시킨다. 웹 애플리케이션 전체의 “요청 흐름 관리자” 역할을 한다.
HandlerMapping URL과 HTTP 메서드(GET, POST 등)에 따라 어느 Controller 메서드가 실행될지 결정하는 컴포넌트다. 요청이 들어오면 DispatcherServlet은 HandlerMapping에게 “담당 컨트롤러 누구야?”라고 묻는다.
HandlerMethodArgumentResolver Controller의 각 파라미터(@RequestParam, DTO, @RequestHeader 등)를 분석해 어떤 데이터를 넣어줄지 결정하는 인터페이스. 이번 실습의 모든 파라미터 바인딩은 이 Resolver 덕분에 가능했다.
Model Controller에서 JSP(View)로 데이터를 전달하기 위한 객체. model.addAttribute("key", value)로 넣은 값은 Request Scope로 JSP에서 ${key} 형태로 사용 가능하다.
ModelAndView Model과 ViewName을 한 객체에 담는 고전적인 방식. mav.addObject()mav.setViewName()을 동시에 설정 가능하다.
ViewResolver Controller가 리턴한 View 이름("viewTest1")을 실제 JSP 경로(/WEB-INF/jsp/viewTest1.jsp)로 변환한다. 파일이 없으면 404 에러를 발생시킨다.
Forward vs Redirect Forward는 서버 내부 이동, Redirect는 브라우저가 새 요청을 보내는 차이. Forward는 주소창이 그대로, Redirect는 변경된다.
---

💬 마무리

오늘 실습의 핵심은 “Spring MVC의 흐름은 단순하다”는 걸 눈으로 확인하는 것.
요청이 들어와 DispatcherServlet을 거쳐 Controller, Model, ViewResolver로 이어지는 흐름을 그릴 수 있으면 MVC는 완전히 잡은 것이다.

다음 단계인 @ModelAttribute, @RequestBody, Validation 등도 사실 이 메커니즘 위에서 확장된 개념이다.