Spring - Controller 속 Handler Method의 매개변수에 대해

제훈·2024년 8월 13일

Spring

목록 보기
9/18

Spring MVC 패턴의 요청 처리 과정이다.

  1. 요청이 들어온다.
  2. 우선 필터를 거쳐서 디스페처 서블릿으로
  3. 핸들러 매핑에서 매핑 후
  4. controller 가기 전 인터셉터 거친 뒤(preHandle) 도착
  5. 비즈니스 로직 처리
  6. 다시 컨트롤러에서 디스페처 서블릿으로 갈 때 인터셉터 (postHandle)
  7. 뷰 리졸버에서 뷰 찾기
  8. 응답

이렇게 있을 때 3번의 핸들러 매핑과 관련해 Controller와 연관지어서 핸들러 메소드의 매개변수에 대해 살펴보자.

@Controller

  • @Controller : @Component의 하위 개념 중 하나로 실행할 때 스프링 컨테이너에 클래스가 등록돼 Controller의 역할을 한다는 것을 스프링 컨테이너에게 인식시켜줄 수 있다.

내부 메소드들은 @Bean 등록을 하지 않아도 된다.
=> Dispatcher Servlet이 핸들러 매핑과 연결되면서 요청에 맞는 Controller를 찾아주기 때문에 내부 메소드를 활용할 수 있다.

기본 세팅
index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1 align="center">핸들러 메소드의 파라미터와 어노테이션</h1>
<h3>1. HttpServletRequest를 요청 파라미터로 전달받기</h3>
<button onclick="location.href='/first/regist'">파라미터 전달하기</button>

<h3>2. @RequestParam을 이용하여 요청 파라미터 전달받기</h3>
<button onclick="location.href='/first/modify'">@RequestParam 이용하기</button>

<h3>3. @ModelAttribute를 이용하여 파라미터 전달받기</h3>
<button onclick="location.href='/first/search'">@ModelAttribute 이용하기</button>

<h3>4. HttpSession 이용하기</h3>
<button onclick="location.href='/first/login'">session 정보 담기</button>

<h3>5. @RequestBody 등의 핸들러 메소드의 어노테이션을 활용한 전달 받기</h3>
<button onclick="location.href='/first/body'">@RequestBody 등의 어노테이션 이용하기</button>
</body>
</html>

messageprinter.html

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

핸들러 메소드의 매개변수

1. @RequestMapping()

  • 클래스에 달린 @RequestMapping()
    • 사용 예시 ex) @RequestMapping("/first")

@RequestMapping() 를 클래스에 적으면 핸들러 메소드들이 모두 /first 경로 아래의 요청들을 받게 될 것이다.

나머지는 메소드에 적을 것임

FirstController

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

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

    /* 반환형이 void인 핸들러 메소드는 요청 경로 자체가 view의 경로 및 이름을 반환한 것으로 바로 해석된다. */
    @GetMapping("/register")
    public void register() {}

    @PostMapping("/register")
    public String registerMenu(HttpServletRequest request, Model model) {
        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";
    }
}

이러한 코드가 있을 때 메소드에 있는 Mapping과 관련된 어노테이션 속 url은 전부 클래스의 /first 아래의 해당하는 것들이다.

즉, @PostMapping("/first/register") 가 본래의 모습인데 저렇게 나타낼 수 있다.

참고 - @PostMapping("/register") 와 @PostMapping("register") 둘다 가능하다. 슬래시가 없어도 된다.


register.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1 align="center">파라미터로 값 전송하기</h1>
<h3>신규 메뉴 등록하기</h3>
<form action="register" 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>
</body>
</html>

index 화면


register 화면


결과 화면


2. @RequestParam

Request의 parameter를 나타낼 때 사용하는 어노테이션으로 컨트롤러에서는 아래와 같이 나타낼 수 있다.

Controller에 추가

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

    @PostMapping("/modify")
    public String modifyMenu(Model model, @RequestParam String name, @RequestParam int modifyPrice) { // @RequestParam은 생략 가능하다.
        String message = name + "메뉴의 가격을 " + modifyPrice + "로 변경하였습니다.";
        model.addAttribute("message", message);

        return "first/messagePrinter";
    }

    @PostMapping("/modify2")
    public String modifyMenu2(Model model, @RequestParam Map<String, String> map) {
        String name = map.get("name2");
        int modifyPrice = Integer.parseInt(map.get("modifyPrice2"));

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

        return "first/messagePrinter";
    }
  • @RequestParam의 속성들
    1. defaultValue : 사용자의 입력값이 없거나 ("") 아니면 request의 parameter 키 값과 일치하지 않는 매개변수일 때 사용하고 싶은 값을 default값으로 설정 가능
    2. name : request parameter의 키 값과 다른 매개변수 명을 사용하고 싶을 때 request parameter의 키 값을 작성한다.

@RequestParam 어노테이션은 생략이 가능하다.

html 부분은 생략하겠다.


index 화면부터 결과 화면까지



2번째는 2000으로 변경되게끔 똑같은 화면을 나타낸다.


3. @ModelAttribute

@ModelAttribute는 HTTP Body 내용과 HTTP 파라미터의 값들을 Getter, Setter, 생성자를 통해 주입하기 위해 사용한다.

일반 변수의 경우 전달이 불가능하기 때문에 model 객체를 통해서 전달해야 한다.

이것을 사용하기 위해서 MenuDTO 클래스를 만들고

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

위 4가지 필드에 대해 기본 생성자, 모든 필드를 매개변수로 갖는 생성자, Getter, Setter, ToString 전부 적어둔 채로 시작했다.

사용 예시 : @ModelAttribute("파라미터명")

하지만 이번 코드에서는 파라미터명에 적어야하는 부분과 menuDTO 부분이 겹치기때문에 따로 적지 않았다.

Controller에 추가

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

    @PostMapping("/search")
    public String searchMenu(@ModelAttribute MenuDTO menuDTO) { //객체를 Model에 담아서 return 하겠다는 뜻이다.
        System.out.println("menuDTO = " + menuDTO);
        return "/first/searchResult";
    }

위와 같이 키 값을 안 넣어주는 경우와 `@ModelAttribute("menu")` 이렇게 적는 경우 **searchResult.html**은 아래처럼 작성해줘야 한다.
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1 align="center">Model에 담긴 커맨드 객체의 정보 출력</h1>

<!-- 설명. 1. @ModelAttribute에 키 값이 없을 경우 커맨드 객체의 타입으로 접근해서 활용(첫 글자 소문자) -->
<!--    <h3 th:text="|메뉴의 이름: ${menuDTO.name}|"></h3>-->
<!--    <h3 th:text="|메뉴의 가격: ${menuDTO.price}|"></h3>-->
<!--    <h3 th:text="|메뉴의 카테고리: ${menuDTO.categoryCode}|"></h3>-->
<!--    <h3 th:text="|메뉴의 판매상태: ${menuDTO.orderableStatus}|"></h3>-->

<!-- 설명. 2. @ModelAttribute에 키 값이 있을 경우 키 값으로 접근해서 활용 -->
<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>

핸들러 메소드의 매개변수에 우리가 작성한 클래스를 스프링이 객체로 만들어 주고 내부적으로 setter를 활용해 값도 주입해준다. (command 객체)


실행 결과 화면

search.html, searchResult.html 파일로 프론트를 간단하게 나타내고 값을 넣었더니 잘 나왔다.


4. HttpSession

이렇게 html로 login 하는 것에 대해 HttpSession을 사용해보고자 한다.


① HttpSession을 매개변수로 선언하기

Controller에 추가

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

    @PostMapping("login")
    public String sessionTest1(HttpSession session, @RequestParam String id) {
        session.setAttribute("id", id);
        return "first/loginResult";
    }

    @GetMapping("logout1")
    public String logoutTest1(HttpSession session) {
        session.invalidate();

        return "first/loginResult";
    }

sessionTest1, logout1은 매개변수로 선언해서 사용한 것이다.

이 때는 로그인도 잘 되고, invalidate()를 통한 로그아웃도 되는 것을 볼 수 있다.

하지만 @SessionAttributes 을 사용하는 방식도 있다.


② @SessionAttribute 사용하기

Controller 클래스에 어노테이션 추가

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

	(중략...)
    
    @PostMapping("login2")
    public String sessionTest2(Model model, @RequestParam String id) {
        model.addAttribute("id", id);

        return "first/loginResult";
    }

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

        return "first/loginResult";
    }
}

이전에 invalidate() 방식으로 로그아웃을 하려고 하면 안 된다. 그 이유는

@SessionAttributes 는 메소드 레벨이 아닌 클래스 레벨에 작성하여 Model로 값을 보낼때 그 name 값과 동일하면 세션으로 인식하여 저장하기 때문인데 그래서 "id"의 값을 지정해준 것이다.

이 때 로그아웃을 하기 위해서는 코드에 적힌 대로 SessionStatus를 매개변수에 넣고 setComplete() 를 사용해야 한다.

대신 이제 id 값을 @SessionAttributes로 지정해준 뒤로는 logout1이 작동하지 않는 것을 알 수 있을 것이다.


로그인 했을 때


로그아웃2을 눌렀을 때


5. @RequestBody 등의 핸들러 메소드의 어노테이션

아래 사진은 body.html의 사진이다.


Controller에 추가

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

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

값을 넣으면 아래처럼 출력된다

profile
백엔드 개발자 꿈나무

0개의 댓글