Spring Framework/Spring Boot

[Spring Boot] 3. Handler Method

hyomee2 2024. 9. 9. 14:52

매핑된 핸들러 메소드로 우리가 입력하는 데이터가 전달되게 하려면 어떻게 해야할까?

핸들러 메서드의 다양한 파라미터 전달 방법에 대해 알아보자.


WebRequest로 요청 파라미터 값 전달받기

첫번째 방법으론 WebRequest로 파라미터 값을 전달받을 수 있다.

HttpServletRequest/Response, ServletRequest/Response도 핸들러 메소드 매개변수로 사용 가능하지만,

이 방식들은 Servlet에 종속이므로, Servlet에 종속적이지 않은 WebRequest가 Spring 기반의 프로젝트에서 더 자주 사용된다.

WebRequest는 Spring Framework에서 제공한다!

 

코드를 통해 알아보자.

 

index.html에서 GET 방식의 /first/regist 요청을 전달한다.

<!--resources/static/index.html-->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index</title>
</head>
<body>
    <h1>핸들러 메소드의 파라미터와 어노테이션</h1>

    <h2>1. WebRequest로 요청 파라미터 전달 받기</h2>
    <button onclick="location.href='/first/regist'">WebRequest</button>
    
</body>
</html>

 

Controller 클래스의 핸들러 메소드를 통해 파라미터 전달 테스트를 위해 값을 입력할 수 있는 view,

여기선 '/first/regist' view를 응답한다.

package org.example.handlermethod;

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

    /* 핸들러 메서드의 반환 값을 void로 설정하면 요청 주소가 곧 view의 이름이 된다.
    * => /first/regist 뷰를 응답한다.
    * => resources/templates/first/regist.html 파일을 만들어서 작업하자.
    * */
    @GetMapping("/regist")
    public void regist(){}
}

 

resources/templates/first 하위에 regist.html 파일을 생성한다.

해당 화면에서 사용자 입력 양식에 값을 입력하고 submit을 누르면 POST 방식의 /first/regist 요청이 발생한다.

<!--resources/templates/first/regist.html -->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>regist</title>
</head>
<body>
<h1>WebRequest로 파라미터 값 전달 받기</h1>
<h3>신규 메뉴 등록하기</h3>
<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>등록하기</button>
</form>
</body>
</html>

 

해당 POST 요청을 매핑할 Controllerhandler method FirstController 클래스에 추가해주자.

package org.example.handlermethod;

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

    /* 핸들러 메서드의 반환 값을 void로 설정하면 요청 주소가 곧 view의 이름이 된다.
    * => /first/regist 뷰를 응답한다.
    * => resources/templates/first/regist.html 파일을 만들어서 작업하자.
    * */
    @GetMapping("/regist")
    public void regist(){}

    @PostMapping("/regist")
    public String registMenu(WebRequest request, Model model) {
        // WebRequest 객체의 getParameter 등의 메서드를 통해 클라이언트로부터 전달된 파라미터를 가져올 수 있다.
        String name = request.getParameter("name");
        int price = Integer.parseInt(request.getParameter("price"));
        int categoryCode = Integer.parseInt(request.getParameter("categoryCode"));

        String message 
            = name + "을(를) 신규 메뉴 목록의 " + categoryCode + "번 카테고리에 " + price + "원으로 등록하였습니다.";
       
        model.addAttribute("message", message);

        return "first/messagePrinter";
    }
}

 

resources/templates/first 하위에 messagePrinter.html 파일을 작성해준다.

<!--/resources/templates/first/messagePrinter.html-->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>messagePrinter</title>
</head>
<body>
  <h3 th:text="${message}"></h3>
</body>
</html>

@RequestParam으로 요청 파라미터 값 전달받기

요청 파라미터를 매핑하여 핸들러 메소드 호출 시 값을 넣어주는 어노테이션으로 매개변수 앞에 작성한다.

 

index.htmlGET 방식의 /first/modify 요청을 전달하는 코드를 추가해주었다.

<!--resources/static/index.html-->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index</title>
</head>
<body>
    <h1>핸들러 메소드의 파라미터와 어노테이션</h1>

    <!-- ... 위에서 작성한 코드는 생략 -->

    <h2>2. @RequestParam으로 요청 파라미터 전달 받기</h2>
    <button onclick="location.href='/first/modify'">@RequestParam</button>
</body>
</html>

 

Controller 클래스의 핸들러 메소드를 통해 파라미터 전달 테스트를 위해 값을 입력할 수 있는 view를 응답한다.

package org.example.handlermethod;

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

    // ... 위에서 작성했던 내용은 생략

    /* <WebRequest 방식>
    /first/modify라는 GET 방식의 요청이 오면 /first/modify라는 view로 응답하겠다
    (/first/modify.html로 처리한다.) */
    @GetMapping("/modify") 
    public void modify() {}
}

 

resources/templates/first 의 하위에 modify.html 파일을 생성해준다.

<!--resources/templates/first/modify.html-->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>modify</title>
</head>
<body>
    <h1>@RequestParam으로 파라미터 값 전달 받기</h1>
    <form action="modify" method="post">
        수정 메뉴명: <input type="text" name="name"/><br>
        수정 메뉴 가격: <input type="number" name="price"/><br>
        <button>수정하기</button>
    </form>
</body>
</html>

 

해당 화면에서 사용자 입력 양식에 값을 입력하고 submit을 누르면 POST 방식의 /first/modify 요청이 발생한다.

이 요청을 매핑할 Controller handler method에서 @RequestParam을 이용해보자.

package org.example.handlermethod;

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

    // ... 위에서 작성한 내용은 생략

    @PostMapping("/modify")
    public String modifyMenu(
            /* 기본적으로 속성과 매개변수 이름은 같아야 한다.
            @RequestParam String name,
            @RequestParam int price, */
            
            /* 그런데 그렇지 않을 경우 아래와 같이 쓸 수 있다.
            @RequestParam(value = "name") String modifyName,
            @RequestParam(value = "price") int modifyPrice,
            */
            
            /* required의 default 값은 true인데, 
            그 말은 이 값이 꼭 이 파라미터가 요청에 반드시 포함돼야 함을 의미한다. 
            아래와 같이 작성하면 nam 파라미터가 요청에 없어도 null로 처리하여 오류가 발생하지 않는다.
            defaultValue의 경우, 요청에 price 파라미터가 포함되지 않을 경우 ""와 같은 빈 문자열이 넘어오게 되는데,
            이 때 parsing 관련 에러가 발생할 수 있다. defaultValue를 이용하면 기본값을 설정해줄 수 있다.*/
            @RequestParam(value = "nam", required = false) String modifyName,
            @RequestParam(value = "price", defaultValue = "0") int modifyPrice,
            
            Model model 
            
    ) {
        String message 
            = modifyName + " 메뉴의 가격을 " + modifyPrice + "원으로 변경하였습니다.";
       
        model.addAttribute("message", message);

        return "first/messagePrinter";
    }
}

 

resources/templates/first 하위에 modify.html 파일에 메뉴 수정하기2를 추가해보자.

<!--/resources/templates/first/modify.html-->

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>modify</title>
</head>
<body>
		
    <!--... 위에서 작성한 내용은 생략-->

    <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>

</body>
</html>

 

해당 화면에서 사용자 입력 양식에 값을 입력하고 submit을 누르면 POST 방식의 /first/modifyAll 요청이 발생한다.

발생하는 요청을 매핑할 ControllerHandler Method이다.

파라미터가 여러 개인 경우 Map으로 한 번에 처리할 수 있는데, 이 때 Map의 키는 formname 속성값이 된다.

package org.example.handlermethod;

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

    // ... 위에서 작성한 내용은 생략

    /* 파라미터가 여러 개인 경우 Map 으로 한 번에 처리할 수 있다. Map의 key는 name 속성이 된다. */
    @PostMapping("/modifyAll")
    public String modifyAllMenu(@RequestParam Map<String, String> parameters, Model model) {

        String name = parameters.get("name");
        int price = Integer.parseInt(parameters.get("price"));

        String message = name + " 메뉴의 가격을 " + price + "원으로 변경하였습니다.";
        model.addAttribute("message", message);

        return "first/messagePrinter";
    }
}

@ModelAttribute로 요청 파라미터 값 전달하기

하나의 덩어리인 MenuDTO 같은 모델들을 커멘드 객체로 전달받을 때 사용하는 어노테이션이다.

 

index.html에서 GET 방식의 /first/search 요청을 전달한다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index</title>
</head>
<body>
    <h1>핸들러 메소드의 파라미터와 어노테이션</h1>

    <!--... 위에서 작성한 내용은 생략-->

    <h2>3. @ModelAttribute으로 요청 파라미터 전달 받기</h2>
    <button onclick="location.href='/first/search'">@ModelAttribute</button>

</body>
</html>

 

Controller 클래스의 핸들러 메소드를 통해 파라미터 전달 테스트를 위해 값을 입력할 수 있는 뷰를 응답한다.

package com.ohgiraffers.handlermethod;

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

    // ... 위에서 작성한 코드는 생략

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

 

resources/templates/first의 하위에 search.html 파일을 생성한다.

<!--resources/templates/first/search.html-->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>search</title>
</head>
<body>
<h1>@ModelAttribute로 파라미터 값 전달 받기</h1>
<h3>메뉴 검색하기</h3>
<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>
    <button>검색하기</button>
</form>
</body>
</html>

해당 화면에서 사용자 입력 양식에 값을 입력하고 submit을 누르면 POST 방식의 /first/search 요청이 발생한다.

 

MenuDTO, FirstController 클래스를 작성해주었다.

package com.ohgiraffers.handlermethod;

/* 커멘드 객체로 사용하기 위해서는 name 속성 값과 필드명이 일치하도록 작성해야 한다. */
public class MenuDTO {
    private String name;
    private int price;
    private int categoryCode;
    private String orderableStatus;

    public MenuDTO(){}

    // lombok 사용안하고 있었으므로 getter, setter 그냥 추가해줌
    public String getName() {
        return name;
    }

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

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public int getCategoryCode() {
        return categoryCode;
    }

    public void setCategoryCode(int categoryCode) {
        this.categoryCode = categoryCode;
    }

    public String getOrderableStatus() {
        return orderableStatus;
    }

    public void setOrderableStatus(String orderableStatus) {
        this.orderableStatus = orderableStatus;
    }

    @Override
    public String toString() {
        return "MenuDTO{" +
                "name='" + name + '\'' +
                ", price=" + price +
                ", categoryCode=" + categoryCode +
                ", orderableStatus='" + orderableStatus + '\'' +
                '}';
    }
}
package com.ohgiraffers.handlermethod;

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

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

    /* 3. @ModelAttribute를 이용하는 방법
    * DTO 같은 모델을 커멘드 객체로 전달 받는 어노테이션으로 Model에 담기는 작업도 자동으로 일어난다.
    * @ModelAttribute 어노테이션은 생략이 가능한데, 생략을 할 경우 model에 클래스명 기준으로 첫글자만 소문자로 바뀌어 담긴다.
    * (이 경우 menuDTO가 담기는 것)
    * model에 담길 때 다른 이름으로 담기길 바라면 어노테이션을 이용하면 된다.(괄호 안에 key값 명시)
    * 그럼 그 key대로 model에 담긴다.*/
    @PostMapping("/search")
    public String searchMenu(@ModelAttribute("menu") MenuDTO menu) {

        return "first/searchResult";
    }
}

 

응답 화면 구성을 위해 resources/templates/first 하위에 searchResult.html 파일을 생성한다.

<!--resources/templates/first/searchResult.html-->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>searchResult</title>
</head>
<body>
    <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>
</body>
</html>

@SessionAttributes로 요청 파라미터 값 전달하기

HttpSession을 전달받는 것도 가능하지만, servlet에 종속적이므로

Spring에서 제공하는 @SessionAttributes를 사용하는 것이 권장된다.

 

클래스 레벨에 @SessionAttributes("모델에 담을 key값")와 같이 지정하면

model에 해당 key가 추가될 때 session에도 자동 등록된다.

 

이제 코드로 살펴보자.

 

index.html에서 GET 방식의 /first/login 요청을 전달한다.

<!--resources/static/index.html-->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index</title>
</head>
<body>

    <!--위에서 작성한 코드는 생략-->

    <h2>4. Session 이용하기</h2>
    <button onclick="location.href='/first/login'">Session 이용하기</button>

</body>
</html>

 

Controller 클래스의 핸들러 메소드를 통해 파라미터 전달 테스트를 위해 값을 입력할 수 있는 뷰를 응답한다.

package org.example.handlermethod;

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

    @GetMapping("/login")
    public void login(){}

 }

 

resources/templates/first의 하위에 login.html 파일을 생성한다.

<!--resources/templates/first/login.html-->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>login</title>
</head>
<body>
    <h1>Session 이용하기</h1>

    <h3>@SessionAttribute</h3>
    <form action="login" method="post">
        id : <input type="text" name="id"/><br>
        pwd : <input type="password" name="pwd"/><br>
        <button>로그인</button>
    </form>

</body>
</html>

해당 화면에서 사용자 입력 양식에 값을 입력하고 submit을 누르면 POST 방식의 /first/login 요청이 발생한다.

 

발생하는 요청을 매핑할 Controller의 핸들러 메소드이다.

package org.example.handlermethod;

@Controller
@RequestMapping("/first/*")
/* 2) 클래스 레벨에 @SessionAttributes("id")를 설정한다.
@SessionAttributes 어노테이션을 이용하여 세션에 값을 담을 key값을 설정해두면
Model 영역에 해당 key로 값이 추가되는 경우 session에 자동 등록을 한다.*/
@SessionAttributes("id") 
public class FirstController {

    @PostMapping("/login")
    public String loginTest(String id, Model model) {

        // 1) model에 id를 추가한다.
        model.addAttribute("id", id);

        return "first/loginResult";
    }  
}

실행을 시켜보면 세션에 값이 잘 저장됨을 확인할 수 있는데, 

이제는 로그아웃 버튼을 누르면 세션 객체가 제거되도록 하는 로직을 작성해보자.

loginResult.html 파일을 작성하자.

<!--resources/templates/first/logiResult.html-->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>loginResult</title>
</head>
<body>
    <h1>Session에 담긴 값 확인</h1>
    <h3 th:text="|${session.id}님 환영합니다.|"></h3>
    <button onclick="location.href='logout'">로그아웃</button>
</body>
</html>

 

SessionAttributes로 등록된 값은

session의 상태를 관리하는 SessionStatussetComplete() 메소드를 호출해야 사용이 만료된다.

package org.example.handlermethod;

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

    // ... 앞에서 작성된 내용은 생략
 
    @GetMapping("/logout")
    public String logout(SessionStatus status) {
        status.setComplete();

        return "first/loginResult";
    }
}

@RequestBody로 요청 파라미터 값 전달하기

@RequestBody 어노테이션은 http 요청의 본문(body)에서 데이터를 읽어와서

모델(자바 객체)로 변환시켜주는 어노테이션이다.

출력해보면 쿼리스트링 형태의 문자열이 전송되며,

JSON으로 전달하는 경우 Jackson의 컨버터로 자동 파싱하여 사용할 수 있다.

주로 RestAPI 작성 시 많이 사용되며, 일반적인 form 전송을 할 때는 거의 사용하지 않는다.

추가적으로 @RequestHeader 어노테이션을 이용해서 헤더 정보를 가져올 수 있으며,

@CookieValue를 이용해서 쿠키 정보도 쉽게 불러올 수 있다.

 

index.html에서 GET 방식의 /first/body 요청을 전달한다.

<!--resources/static/index.html-->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index</title>
</head>
<body>

    <!-- ... 위에서 작성한 내용 생략-->

    <h2>5. @RequestBody 이용하기 </h2>
    <button onclick="location.href='/first/body'">@RequestBody 이용하기</button>
</body>
</html>

 

Controller 클래스의 핸들러 메소드를 통해 파라미터 전달 테스트를 위해 값을 입력할 수 있는 뷰를 응답한다.

package org.example.handlermethod;

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

    @GetMapping("/body")
    public void body(){}

}

 

resources/templates/first 하위에 body.html 파일을 생성한다.

<!--resources/templates/first/body.html-->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>body</title>
</head>
<body>
<h1>@RequestBody 이용하기</h1>
<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>
    <button>검색하기</button>
</form>
</body>
</html>

해당 화면에서 사용자 입력 양식에 값을 입력하고 submit을 누르면 POST 방식의 /first/body 요청이 발생한다.

 

요청을 매핑할 Controller의 핸들러 메소드를 아래와 같이 작성할 수 있다.

package org.example.handlermethod;

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

    // 위에서 작성한 내용 생략

    @PostMapping("/body")
    public void bodyTest(
            @RequestBody String body,
            @RequestHeader("content-type") String contentType,
            @CookieValue("JSESSIONID") String sessionId
    ) {
        System.out.println("body: " + body);
        System.out.println("contentType: " + contentType);
        System.out.println("sessionId: " + sessionId);
    }
}

실행해보면 클라이언트에서 입력된 값이

@RequestBody 어노테이션을 설정한 값으로 잘 전달됨을 콘솔 출력을 통해 확인할 수 있다.