ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [김영한 스프링] 24. 스프링 MVC 기본 기능 - HTTP 요청 파라미터
    Spring/스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 2023. 7. 14. 02:24

    HTTP 요청 파라미터 - 쿼리 파라미터, HTML Form

     

    HTTP 요청 데이터 조회 - 개요

    클라이언트에서 서버로 요청 데이터를 전달할 때는 주로 다음 3가지 방법을 사용한다.

    • GET - 쿼리 파라미터
      • /url?username=hello&age=20
      • 메시지 바디 없이, URL의 쿼리 파라미터에 데이터를 포함해서 전달
      • 예) 검색, 필터, 페이징등에서 많이 사용하는 방식
    • POST - HTML Form
      • content-type: application/x-www-form-urlencoded
      • 메시지 바디에 쿼리 파리미터 형식으로 전달 username=hello&age=20
      • 예) 회원 가입, 상품 주문, HTML Form 사용
    • HTTP message body에 데이터를 직접 담아서 요청
      • HTTP API에서 주로 사용, JSON, XML, TEXT
      • 데이터 형식은 주로 JSON 사용
      • POST, PUT, PATCH

     

     

    HTTP 요청 데이터 조회 - 개요

    HttpServletRequest의 request.getParameter()를 사용하면 다음 두가지 요청 파라미터를 조회할 수 있다.

     

    GET, 쿼리 파라미터 전송

    예시

    http://localhost:8080/request-param?username=hello&age=20

     

    POST, HTML Form 전송

    예시

    POST /request-param ...
    content-type: application/x-www-form-urlencoded
    username=hello&age=20

     

    GET 쿼리 파리미터 전송 방식이든, POST HTML Form 전송 방식이든 둘다 형식이 같으므로 구분 없이 조회할 수 있다.

    이것을 간단히 요청 파라미터(request parameter) 조회라 한다.

     

     

    RequestParamController

    package hello.springmvc.basic.request;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    @Slf4j
    @Controller
    public class RequestParamController {
    
        @RequestMapping("/request-param-v1")
        public void requestParamV1(HttpServletRequest request, HttpServletResponse response) throws IOException {
            String username = request.getParameter("username");
            int age = Integer.parseInt(request.getParameter("age"));
    
            log.info("username={}, age={}", username, age);
    
            response.getWriter().write("ok");
        }
    }

    main/java/hello/springmvc/basic/request/RequestParamController 생성

     

    request.getParameter()

    여기서는 단순히 HttpServletRequest가 제공하는 방식으로 요청 파라미터를 조회했다.

     

     

    실행

     

     

    결과

     

     

    Post Form 페이지 생성

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head><body>
        <form action="/request-param-v1" method="post">
            username: <input type="text" name="username" />
            age: <input type="text" name="age" />
            <button type="submit">전송</button>
        </form>
    </body>
    </html>

    main/resources/static/basic/hello-form.html 생성

     

     

    실행

     

     

    결과

     

    참고
    Jar를 사용하면 webapp 경로를 사용할 수 없다. 이제부터 정적 리소스도 클래스 경로에 함께 포함해야 한다.

     

     

    HTTP 요청 파라미터 - @RequestParam

     

    requestParamV2

        /**
         * @RequestParam 사용
         * - 파라미터 이름으로 바인딩
         * @ResponseBody 추가
         * - View 조회를 무시하고, HTTP message body에 직접 해당 내용 입력
         */
        @ResponseBody
        @RequestMapping("/request-param-v2")
        public String requestParamV2(@RequestParam("username") String memberName, @RequestParam("age") int memberAge){
    
            log.info("username={}, age={}", memberName, memberAge);
    
            return "ok";
        }

    @Controller 애노테이션을 사용하고 있어서 return "ok" 뷰를 찾게 됨

    그래서 @RestController 애노테이션으로 변경하는 방법도 있지만 @ResponseBody 애노테이션을 사용해서 @RestController 애노테이션과 같은 효과를 냄

     

    @RequestParam : 파라미터 이름으로 바인딩

    @ResponseBody : View 조회를 무시하고, HTTP message body에 직접 해당 내용 입력

     

    @RequestParam의 name(value) 속성이 파라미터 이름으로 사용

    @RequestParam("username") String memberName -> request.getParameter("username")

     

     

    실행

     

     

    결과

     

     

    requestParamV3

        /**
         * @RequestParam 사용
         * HTTP 파라미터 이름이 변수 이름과 같으면 @RequestParam(name="xx") 생략 가능
         */
        @ResponseBody
        @RequestMapping("/request-param-v3")
        public String requestParamV3(@RequestParam String username, @RequestParam int age){
    
            log.info("username={}, age={}", username, age);
    
            return "ok";
        }

    HTTP 파라미터 이름이 변수 이름과 같으면 @RequestParam(name="xx") 생략 가능

     

     

    실행

     

     

    결과

     

     

    requestParamV4

        /**
         * @RequestParam 사용
         *  String, int 등의 단순 타입이면 @RequestParam 도 생략 가능
         */
        @ResponseBody
        @RequestMapping("/request-param-v4")
        public String requestParamV4(String username, int age){
    
            log.info("username={}, age={}", username, age);
    
            return "ok";
        }

    String , int , Integer 등의 단순 타입이면 @RequestParam도 생략 가능

     

    주의
    @RequestParam 애노테이션을 생략하면 스프링 MVC는 내부에서 required=false를 적용한다.
    required 옵션은 바로 다음에 설명한다.

     

     

    실행

     

     

    결과

     

     

    파라미터 필수 여부 - requestParamRequired

        /**
         * @RequestParam.required
         * /request-param-required -> username이 없으므로 예외
         *
         * 주의!
         * /request-param-required?username= -> 빈문자로 통과
         *
         * 주의!
         * /request-param-required
         * int age -> null을 int에 입력하는 것은 불가능, 따라서 Integer 변경해야 함(또는 다음에 나오는 defaultValue 사용)
         */
        @ResponseBody
        @RequestMapping("/request-param-required")
        public String requestParamRequired(@RequestParam(required = true) String username,
                                           @RequestParam(required = false) int age){
    
            log.info("username={}, age={}", username, age);
    
            return "ok";
        }

    • @RequestParam.required
      • 파라미터 필수 여부
      • 기본값이 파라미터 필수(true)이다.

     

     

    실행

    • /request-param 요청
      • username이 없으므로 400 예외가 발생한다.

     

     

    주의! - 기본형(primitive)에 null 입력

    • /request-param 요청
    • @RequestParam(required = false) int age

    null을 int에 입력하는 것은 불가능(500 예외 발생)

    따라서 null을 받을 수 있는 Integer로 변경하거나, 또는 다음에 나오는 defaultValue사용

     

     

        /**
         * @RequestParam.required
         * /request-param-required -> username이 없으므로 예외
         *
         * 주의!
         * /request-param-required?username= -> 빈문자로 통과
         *
         * 주의!
         * /request-param-required
         * int age -> null을 int에 입력하는 것은 불가능, 따라서 Integer 변경해야 함(또는 다음에 나오는 defaultValue 사용)
         */
        @ResponseBody
        @RequestMapping("/request-param-required")
        public String requestParamRequired(@RequestParam(required = true) String username,
                                           @RequestParam(required = false) Integer age){
    
            log.info("username={}, age={}", username, age);
    
            return "ok";
        }

    int에 null이 들어가면 컴파일 오류가 발생하기 때문에 Integer로 변경

     

     

     

     

    주의! - 파라미터 이름만 사용

    /request-param?username=

    파라미터 이름만 있고 값이 없는 경우 -> 빈문자로 통과

     

     

    기본 값 적용 - requestParamDefault

        /**
         * @RequestParam
         * - defaultvalue 사용
         *
         * 참고 : defaultValue는 빈 문자의 경우에도 적용
         * /request-param-default?username=
         */
        @ResponseBody
        @RequestMapping("/request-param-default")
        public String requestParamDefault(@RequestParam(required = true, defaultValue = "guest") String username,
                                           @RequestParam(required = false, defaultValue = "-1") int age){
    
            log.info("username={}, age={}", username, age);
    
            return "ok";
        }

    파라미터에 값이 없는 경우 defaultValue를 사용하면 기본 값을 적용할 수 있다.

    이미 기본 값이 있기 때문에 required는 의미가 없다.

     

    defaultValue는 빈 문자의 경우에도 설정한 기본 값이 적용된다.

    /request-param-default?username=

     

     

    실행

     

     

    결과

     

     

    파라미터를 Map으로 조회하기 - requestParamMap

        /**
         * @RequestParam Map, MultiValueMap
         * Map(key=value)
         * MultiValueMap(key=[value1, value2, ...]) ex) (key=userIds, value=[id1, id2])
         */
        @ResponseBody
        @RequestMapping("/request-param-map")
        public String requestParamMap(@RequestParam Map<String, Object> paramMap){
    
            log.info("username={}, age={}", paramMap.get("username"), paramMap.get("age"));
    
            return "ok";
        }

     

     

    실행

     

     

    결과

    파라미터를 Map, MultiValueMap으로 조회할 수 있다.

     

    • @RequestParam Map,
      • Map(key=value)
    • @RequestParam MultiValueMap
      • MultiValueMap(key=[value1, value2, ...] ex) (key=userIds, value=[id1, id2])

    파라미터의 값이 1개가 확실하다면 Map을 사용해도 되지만, 그렇지 않다면 MultiValueMap을 사용하자.

     

     

    HTTP 요청 파라미터 - @ModelAttribute

     

    package hello.springmvc.basic;
    
    import lombok.Data;
    
    @Data
    public class HelloData {
    
        private String username;
        private int age;
    }

    • 롬복 @Data
      • @Getter , @Setter , @ToString , @EqualsAndHashCode , @RequiredArgsConstructor를 자동으로 적용해 준다.

     

     

    @ModelAttribute 적용 - modelAttributeV1

        /**
         * @ModelAttribute 사용
         * 참고: model.addAttribute(helloData) 코드도 함께 자동 적용됨, 뒤에 model을 설명할 때자세히 설명
         */
        @ResponseBody
        @RequestMapping("/model-attribute-v1")
        public String modelAttributeV1(@RequestParam String username, @RequestParam int age){
    
            HelloData helloData = new HelloData();
            helloData.setUsername(username);
            helloData.setAge(age);
    
            log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
            log.info("helloData={}", helloData);
    
            return "ok";
        }

     

     

    실행

     

     

    결과

     

     

    @Data에 ToString이 들어있어 log.info("helloData={}", helloData);가 가능

     

     

        /**
         * @ModelAttribute 사용
         * 참고: model.addAttribute(helloData) 코드도 함께 자동 적용됨, 뒤에 model을 설명할 때자세히 설명
         */
        @ResponseBody
        @RequestMapping("/model-attribute-v1")
        public String modelAttributeV1(@ModelAttribute HelloData helloData){
    
            log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
    
            return "ok";
        }

     

     

    실행

     

     

    결과

    HelloData 객체가 생성되고, 요청 파라미터의 값도 모두 들어가 있다.

     

    스프링MVC는 @ModelAttribute가 있으면 다음을 실행한다.

    • HelloData 객체를 생성한다.
    • 요청 파라미터의 이름으로 HelloData 객체의 프로퍼티를 찾는다. 그리고 해당 프로퍼티의 setter를 호출해서 파라미터의 값을 입력(바인딩) 한다.
    • 예) 파라미터 이름이 username이면 setUsername() 메서드를 찾아서 호출하면서 값을 입력한다.

     

    프로퍼티

    객체에 getUsername(), setUsername() 메서드가 있으면, 이 객체는 username이라는 프로퍼티를 가지고 있다.

    username 프로퍼티의 값을 변경하면 setUsername()이 호출되고, 조회하면 getUsername()이 호출된다.

    class HelloData {
        getUsername();
        setUsername();
    }

     

    바인딩 오류

    age=abc처럼 숫자가 들어가야 할 곳에 문자를 넣으면 BindException이 발생한다. 이런 바인딩 오류를 처리하는 방법은 검증 부분에서 다룬다.

     

     

    @ModelAttribute 생략 - modelAttributeV2

        /** * @ModelAttribute 생략 가능
         * String, int 같은 단순 타입 = @RequestParam
         * argument resolver 로 지정해둔 타입 외 = @ModelAttribute
         */
        @ResponseBody
        @RequestMapping("/model-attribute-v2")
        public String modelAttributeV2(HelloData helloData){
    
            log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
    
            return "ok";
        }

    @ModelAttribute는 생략할 수 있다.

    그런데 @RequestParam도 생략할 수 있으니 혼란이 발생할 수 있다.

     

    스프링은 해당 생략 시 다음과 같은 규칙을 적용한다.

    • String, int, Integer 같은 단순 타입 = @RequestParam
    • 나머지 = @ModelAttribute(argument resolver로 지정해둔 타입 외)

     

     

    실행

     

     

    결과

     

     

    출처 : https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1

     

    스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의

    웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., 원

    www.inflearn.com

Designed by Tistory.