Handler Method

컨테이너·2025년 11월 29일

Spring Boot

목록 보기
3/7
post-thumbnail

1. Handler Method 정리 (파라미터 받는 다양한 방법)

핸들러 메서드는 사용자 요청을 받아서 응답을 만드는 메서드.

스프링이 이 메서드를 호출할 때, 메서드 파라미터 타입을 보고 자동으로 (인자)값을 넣어준다.

예:

  • WebRequest, HttpServletRequest, HttpSession 같은 것들을 파라미터에 선언해두면 스프링이 알아서 객체를 넣어 줌
  • @RequestParam, @ModelAttribute, @RequestBody 등을 붙이면 요청 파라미터를 알맞게 변환해서 넣어 줌

각 방식 마다 어느 시점에 사용하면 좋을지 파악해보자


1-1. WebRequest

흐름부터 보면:

  1. index.html에서 /first/regist로 GET 요청을 보냄
  2. 컨트롤러가 그 요청을 받아서 regist.html 화면을 보여줌
  3. regist.html에서 form을 제출하면 /first/regist로 POST 요청이 들어감
  4. POST 요청을 처리하는 메소드에서 WebRequest로 파라미터를 직접 꺼냄

컨트롤러:

@Controller
@RequestMapping("/first/*")
public class FirstController {

    /* 반환타입이 void면
     * 요청 주소(/first/regist)가 그대로 뷰 이름이 된다.
     * => templates/first/regist.html 을 찾아감
     */
    @GetMapping("regist")
    public void regist() {}
}
  • tip: 핸들러 메소드 반환타입이 void 이면 요청 주소가 그대로 뷰 이름이 된다.

뷰(regist.html):

<form action="regist" method="post">
    등록할 메뉴의 이름 : <input type="text" name="name"><br>
    등록할 메뉴의 가격 : <input type="number" name="price"><br>
    등록할 메뉴의 카테고리 :
    <select name="categoryCode">
        <option value="1">식사</option>
        <option value="2">음료</option>
        <option value="3">디저트</option>
    </select><br>
    <button type="submit">등록하기</button>
</form>

POST 요청 처리:

@PostMapping("regist")
public String registMenu(Model model, WebRequest request) {

    // WebRequest로 파라미터 직접 꺼냄
    String name = request.getParameter("name");
    int price = Integer.parseInt(request.getParameter("price"));
    int categoryCode = Integer.parseInt(request.getParameter("categoryCode"));

    String message =
            name + "을(를) 신규 메뉴 목록의 " + categoryCode +
            "번 카테고리에 " + price + "원으로 등록 하셨습니다!";
    System.out.println(message);

    model.addAttribute("message", message);

    return "first/messagePrinter";
}

정리: WebRequest로 요청 파라미터 전달 받기

  • WebRequest =~ HttpServletRequest 비슷한 느낌이지만, 스프링 쪽에 속한 타입
  • 사용 방법 : request.getParameter("name") 이런 식으로 직접 꺼내서 사용
  • 파라미터가 많아지면 매번 getParameter() 호출해야 해서 조금 번거로울 수 있음
  • HttpServletRequest/HttpServletResponse 도 그대로 파라미터에 선언해서 받을 수 있음 HttpsServletRequest/Response는 Servlet API에 종속적이지만 WebRequest는 스프링 API라서 프로젝트에서 더 자주 사용된다.

→ Http Request , Response 과 동일하지만 스프링에서는 WebRequest 를 사용하면 된다.


1-2. @RequestParam

스프링에서 제공하는, 변수를 하나씩 불러오는 어노테이션이다.

  • 요청 파라미터를 매핑하여 핸들러 메소드 호출 시 값을 넣어주는 어노테이션으로 매개변수 앞에 작성한다.
  • name 속성과 매개변수명이 다른 경우 @RequestParam("name") 으로 작성하며 별도의 속성이 필요 없을 경우에는 어노테이션 생략도 가능하다.
  • 전달하는 name 속성과 일치하는 것이 없는 경우 400 (Bad Request) 에러가 발생하는데 이는 required = true 가 기본 값이기 때문이다.
  • 값을 입력하지 않고 넘기면 빈 문자열이 넘어오므로 parsing 관련 에러가 발생할 수 있다.

흐름:

  1. /first/modify GET 요청 → modify.html 화면 보여줌
  2. modify.html에서 form 전송 → /first/modify POST
  3. POST 메소드에서 @RequestParam으로 각 값 받기

컨트롤러 GET:

@GetMapping("modify")
public void modify() {}

뷰(modify.html):

<h3>메뉴 수정하기</h3>
<form action="modify" method="post">
    수정할 메뉴의 이름 : <input type="text" name="modifyName"><br>
    수정할 메뉴의 가격 : <input type="number" name="modifyPrice"><br>
    <button type="submit">수정하기</button>
</form>

POST 처리:

@PostMapping("modify")
public String modifyMenuPrice(
        Model model,
        @RequestParam(value="name", required = false) String modifyName,
        @RequestParam(value="price", defaultValue = "0") int modifyPrice) {

    String message = modifyName + "메뉴의 가격을 " + modifyPrice + "로 가격을 변경하였습니다.";
    System.out.println(message);

    model.addAttribute("message", message);

    return "first/messagePrinter";
}

⭐여기서 중요한 속성들:

  • @RequestParam("modifyName") String name → name 속성이 modifyName인 값을 해당 변수로 매핑 → 변수 이름이 폼의 name과 다르면 "이름"으로 명시
    @RequestParam("name") String modifyName
  • required = true (기본값) → 요청에 이 파라미터가 없으면 400 에러. 반드시 값을 넣어주어야 한다.
  • required = false → 없어도 null로 처리. 값이 안들어와도 괜찮다.
  • defaultValue = "0" → 값이 아예 안 넘어와도 0으로 세팅됨 (파싱 에러 방지)

@RequestParm 을 생략할 수도 있다.

  • 하지만 권장되지는 않는다.

1.2.2 파라미터 여러 개를 한 번에 Map으로 받는 것도 가능:

뷰의 두 번째 form:

<h3>메뉴 수정하기2</h3>
<form action="modifyAll" method="post">
    수정할 메뉴의 이름 : <input type="text" name="modifyName2"><br>
    수정할 메뉴의 가격 : <input type="number" name="modifyPrice2"><br>
    <button type="submit">수정하기</button>
</form>

컨트롤러:

@PostMapping("modifyAll")
public String modifyMenu(Model model,
                         @RequestParam Map<String, String> parameters) {

    String modifyMenu = parameters.get("modifyName2");
    int modifyPrice = Integer.parseInt(parameters.get("modifyPrice2"));

    String message = "메뉴의 이름을 " + modifyMenu +
                     "(으)로, 가격을 " + modifyPrice + "원 으로 변경하였습니다.";
    System.out.println(message);

    model.addAttribute("message", message);

    return "first/messagePrinter";
}

포인트:

  • Map의 key = form의 name 속성
  • 파라미터 개수가 많고, 이름을 반복해서 변수로 받기 귀찮을 때 편리
  • 하지만 DTO 쓰는 게 더 깔끔한 경우가 많음 (아래 @ModelAttribute)

1-3. @ModelAttribute (커맨드 객체)

이번에는 폼 입력 전체를 DTO로 한 번에 받는 방식이다.

  • 커맨드 객체로 사용하기 위해서는 name 속성 값과 필드명이 일치하도록 작성해야 한다.

흐름:

  1. /first/search GET → search.html 보여줌
  2. search.html에서 여러 값 입력 후 POST /first/search
  3. 컨트롤러에서 DTO(MenuDTO)로 한 번에 바인딩

GET:

@GetMapping("search")
public void search() {}

뷰(search.html):

<form action="search" method="post">
    검색할 메뉴 이름 : <input type="text" name="name"><br>
    검색할 메뉴 가격 : <input type="number" name="price"><br>
    검색할 메뉴의 카테고리 :
    <select name="categoryCode">
        <option value="1">식사</option>
        <option value="2">음료</option>
        <option value="3">디저트</option>
    </select><br>
    검색할 판매 상태 : <input type="text" name="orderableStatus"><br>
    <input type="submit" value="검색하기">
</form>

커맨드 객체 DTO:

public class MenuDTO {

    private String name;
    private int price;
    private int categoryCode;
    private String orderableStatus;

    public MenuDTO() {}

    public MenuDTO(String name, int price, int categoryCode, String orderableStatus) {
        this.name = name;
        this.price = price;
        this.categoryCode = categoryCode;
        this.orderableStatus = orderableStatus;
    }

    // getter/setter 전부 존재해야 함 (스프링이 자동으로 호출)
    // ...
}

핸들러 메소드:

@PostMapping("search")
public String searchMenu(@ModelAttribute("menu") MenuDTO menu) {

    System.out.println(menu);

    return "first/searchResult";
}

뷰(searchResult.html):

<h1>Model에 담긴 커맨드 객체의 정보 출력</h1>
<h3 th:text="|메뉴의 이름 : ${ menu.name }|"></h3>
<h3 th:text="|메뉴의 가격 : ${ menu.price }|"></h3>
<h3 th:text="|메뉴의 카테고리 : ${ menu.categoryCode }|"></h3>
<h3 th:text="|메뉴의 판매상태 : ${ menu.orderableStatus }|"></h3>

포인트:

  • @ModelAttribute가 붙은 객체는
    1. 요청 파라미터를 필드에 바인딩해서 객체 생성
    2. 그 객체를 Model에도 자동으로 담아준다
  • "menu"라는 이름으로 모델에 들어감 (지정 안 하면 타입 이름의 앞글자 소문자: MenuDTO → menuDTO)
  • 이를 사용 안하고 DTO가 커맨드 객체로 쓰이려면
    • 기본 생성자 필수
    • 폼 name과 필드 이름 일치
    • 적절한 setter 메서드 필요

→ 폼 데이터가 많아질수록 @ModelAttribute + DTO 방식이 훨씬 깔끔하고 유지보수하기 좋다.


1-4. HttpSession / @SessionAttributes / SessionStatus

로그인 정보 같은 걸 세션에 어떻게 담고 빼는지를 다룬다. 이전에 Session을 다룰 때 객체를 직접 생성하는 것이 아니라 request 객체 안에 getSession() 을 통해서 뽑아서 사용했었다.

세션에 있는 정보를 가져다 쓰거나 값을 추가해서 사용하려면 servlet에서는

HttpSession session = request.getSession() 처럼 사용했다.

HttpSession을 전달받는 것도 가능하지만 Servlet에 종속적이므로 Spring에서 제공하는 기능을 사용하는 것을 권장한다.

1) HttpSession 직접 사용

흐름:

  1. /first/login GET → login.html
  2. login.html에서 login1 form 제출 → /first/login1 POST
  3. 로그인 성공 시 session.setAttribute("id", id) 로 저장
  4. loginResult.html에서 session.id 출력
  5. /first/logout1 요청 시 session.invalidate() 로 세션 전체 제거

login.html:

<h3>HttpSession을 매개변수로 선언하기</h3>
<form action="login1" method="post">
    ID : <input type="text" name="id"><br>
    PWD : <input type="password" name="pwd"><br>
    <input type="submit" value="로그인">
</form>

컨트롤러:

@PostMapping("login1")
public String sessionTest(HttpSession session, @RequestParam String id) {

    session.setAttribute("id", id);

    return "first/loginResult";
}

@GetMapping("logout1")
public String logoutTest(HttpSession session) {
    session.invalidate();  // 세션 전체 종료
    return "first/loginResult";
}

뷰(loginResult.html):

<h1>Session에 담긴 값 확인하기</h1>
<h3 th:text="|${ session.id }님 환영합니다.|"></h3>
<div>
    <button onclick="location.href='logout1'">로그아웃</button>
</div>

포인트:

  • HttpSession을 파라미터에 선언하면 스프링이 세션 객체를 주입
  • setAttribute/getAttribute로 직접 다루는 전통적인 방식
  • invalidate()는 세션 자체를 날리는 것 (모든 값 삭제)

2) @SessionAttributes + Model + SessionStatus

  • 클래스 레벨에 @SwessionAttribute("key") 와 같이 지정하면 Model에 해당 key가 추가될 경우 Session에도 자동 등록된다.

이번에는 “모델에 넣었다가 자동으로 세션에 올리기” 방식.

login.html에 두 번째 form 추가:

<h3>@SessionAttributes를 사용하기</h3>
<form action="login2" method="post">
    ID : <input type="text" name="id"><br>
    PWD : <input type="password" name="pwd"><br>
    <input type="submit" value="로그인">
</form>

컨트롤러 상단에:

@Controller
@RequestMapping("/first/*")
@SessionAttributes("id")
public class FirstController {
    ...
}

그리고 핸들러:

@PostMapping("login2")
public String sessionTest2(Model model, @RequestParam String id) {

    model.addAttribute("id", id);  // Model 에 추가

    return "first/loginResult";
}

여기서 중요한 동작:

  • @SessionAttributes("id") 때문에 Model에 "id"라는 이름의 값이 들어오면 → 세션에도 자동으로 저장됨
  • 즉, 매번 session.setAttribute 안 해도 됨

로그아웃2 버튼 추가:

<button onclick="location.href='logout2'">로그아웃2</button>

컨트롤러:

@GetMapping("logout2")
public String logoutTest2(SessionStatus sessionStatus) {

    // @SessionAttributes로 관리되는 세션 값들을 사용 끝 처리
    sessionStatus.setComplete();

    return "first/loginResult";
}

⭐포인트:

  • @SessionAttributes는 “이 컨트롤러에서 Model에 이 이름으로 담는 건 세션에도 저장해라”라는 의미
  • SessionStatus.setComplete()를 호출해야 그 세션 값이 “만료”됨
    • invalidate()처럼 세션 전체를 날리는 게 아니라 이 컨트롤러에서 관리하던 세션 속성만 끝내는 개념
    • invalidate() 를 사용해도 Session이 소멸되지 않는다.
  • 주의: @SessionAttributes는 컨트롤러 단위로 동작

1-5. @RequestBody (+ @RequestHeader, @CookieValue)

HTTP body를 그대로 읽어오는 것.

흐름:

  1. /first/body GET → body.html
  2. body.html의 form을 POST /first/body로 전송
  3. 컨트롤러에서 @RequestBody로 raw body 문자열을 받음
  4. @RequestHeader, @CookieValue로 헤더/쿠키도 확인

body.html:

<form action="body" method="post">
    메뉴 이름 : <input type="text" name="name"><br>
    메뉴 가격 : <input type="number" name="price"><br>
    카테고리 :
    <select name="categoryCode">
        <option value="1">식사</option>
        <option value="2">음료</option>
        <option value="3">디저트</option>
    </select><br>
    판매 상태 : <input type="text" name="orderableStatus"><br>
    <input type="submit" value="검색하기">
</form>

컨트롤러:

@PostMapping("body")
public void bodyTest(@RequestBody String body,
                     @RequestHeader("content-type") String contentType,
                     @CookieValue(value="JSESSIONID", required = false) String sessionId) {

    System.out.println(contentType);     // 요청의 Content-Type
    System.out.println(sessionId);       // 세션 ID 쿠키 값
    System.out.println(body);            // raw body (쿼리스트링 형식)
    System.out.println(URLDecoder.decode(body));
}

여기서 body는 이런 문자열로 들어온다:

name=%ED%83%9C%EA%B5%AD%EB%8B%AD&price=10000&categoryCode=1&orderableStatus=Y

URLDecoder로 디코딩하면 사람이 읽을 수 있는 한글/숫자로 변환 가능.

실제로 REST API에서 JSON을 보낼 때:

  • Content-Type: application/json
  • @RequestBody MenuDTO menu 이런 식으로 DTO로 직접 받을 수 있음 (Jackson이 JSON → 객체로 자동 변환)
  • 비동기를 다룰 수 있다(javascript를 통해서)

포인트:

  • @RequestBody = “body 전체를 이 타입으로 파싱해서 줘”
  • form-url-encoded도 받을 수 있지만, 이건 보통 @RequestParam / @ModelAttribute로 더 많이 처리
  • JSON / XML 같은 바디 기반 요청에서는 @RequestBody가 핵심
  • @RequestHeader = 특정 헤더값 꺼내기
  • @CookieValue = 특정 쿠키값 꺼내기

정리

  • WebRequest / HttpServletRequest → request 객체 직접 건드리고 싶을 때
  • @RequestParam → 단순 파라미터 몇 개 받을 때
  • @ModelAttribute + DTO → 폼 데이터가 많을 때, 바로 객체로 바인딩해서 쓰고 싶을 때
  • HttpSession / @SessionAttributes → 로그인 정보 등 서버 세션에 저장해야 할 때
  • @RequestBody → REST API처럼 body(json 등)를 그대로 받아서 객체로 파싱할 때

profile
백엔드

0개의 댓글