스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 23

Spring 정리 2022. 5. 22. 16:11

인프런 강의 37일차.

 - 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 1 (김영한 강사님)

 - 서블릿, JSP, MVC 패턴

 - 서블릿으로 1차 구현

   -> 서블릿으로 구현했을 때 불편한 점 개선을 위해 JSP로 2차 구현

   -> JSP로도 불편한 점을 개선하기 위해 MVC 패턴으로 3차 구현

 - 스프링 프로젝트 시작

   -> 스프링 기본기능 이해

   -> 스프링 MVC 웹페이지 만들기

 

4. 스프링 MVC - 기본 기능

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

  > HTTP 요청 데이터 조회 - 개요

   - 서블릿에서 학습했던 HTTP 요청 데이터를 조회 하는 방법을 다시 떠올려보자.

   - 그리고 서블릿으로 학습했던 내용을 스프링이 얼마나 깔끔하고 효율적으로 바꾸어주는지 알아보자.

   - 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

 

  > 스프링으로 요청 파라미터를 조회하는 방법

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");
    }
}

  - hello.springmvc.basic.request.RequestParamController.java

  - HTTP get/post 호출 처리

 

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

  - resources/static/basic/hello-form.html

  - HTTP post 호출을 하기 위한 샘플 html

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

 

 4.6 HTTP 요청 파라미터 - @RequestParam

  > 스프링이 제공하는 @RequestParam 을 사용하면 요청 파라미터를 매우 편리하게 사용할 수 있다. 

/**
 * @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";            //String 반환일 경우 ViewResolver를 호출하는 것이지만 ResponseBody Annotation을 추가함으로써 REST API 호출로 변경할 수 있다.
}

  - requestParamV2

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

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

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

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

/**
 * @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";
}

  - requestParamV3

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

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

  - requestParamV4

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

  * @RequestParam 애노테이션을 생략하면 스프링 MVC는 내부에서 required=false 를 적용한다.

  - @RequestParam을 생략하지 않음으로써 명확하게 요청 파리미터에서 데이터를 읽는 다는 것을 표현하기도 한다.

/**
 * @RequestParam.required
 * /request-param -> username이 없으므로 예외
 *
 * 주의!
 * /request-param?username= -> 빈문자로 통과
 *
 * 주의!
 * /request-param
 * 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";
}

  - requestParamRequired (파라미터 필수 여부)

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

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

  - 파라미터 이름만 사용 : /request-param?username= 파라미터 이름만 있고 값이 없는 경우 빈문자로 통과

  - 기본형(primitive)에 null 입력 : /request-param 요청 @RequestParam(required = false) int age

  - null 을 int 에 입력하는 것은 불가능(500 예외 발생). null 을 받을 수 있는 Integer 로 변경 혹은 defaultValue 사용

/**
 * @RequestParam
 * - defaultValue 사용
 *
 * 참고: defaultValue는 빈 문자의 경우에도 적용
 * /request-param?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";
}

  - requestParamDefault (기본 값 적용)

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

  - defaultValue 는 빈 문자의 경우에도 설정한 기본 값이 적용된다. (e.g. /request-param?username=)

/**
 * @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";
}

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

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

  - @RequestParam Map : Map(key=value)

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

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

 

 4.7 HTTP 요청 파라미터 - @ModelAttribute

  > 실제 개발을 하면 아래와 같이 요청 파라미터를 받아서 필요한 객체를 만들고 그 객체에 값을 넣어주어야 한다.

@RequestParam String username;
@RequestParam int age;
HelloData data = new HelloData();
data.setUsername(username);
data.setAge(age);

  - 스프링은 이 과정을 완전히 자동화해주는 @ModelAttribute 기능을 제공한다

package hello.springmvc.basic;
import lombok.Data;

@Data
public class HelloData {
    private String username;
    private int age;
}

  - @Data Annotation의 역할 : 롬복에서 제공해주는 @Data의 경우 @Getter , @Setter , @ToString , @EqualsAndHashCode , @RequiredArgsConstructor 를 자동으로 적용해준다.

/**
 * @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);        //객체를 log에 찍을 경우 @Data에서 ToString으로 변환해준다.
    return "ok";
}

@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData) {
    //@ModelAttribute HelloData helloData의 경우 @RequestParam String username, @RequestParam int age와 동일한 역할을 한다.
    log.info("username={}, age={}", helloData.getUsername(),
            helloData.getAge());
    return "ok";
}

  - @ModelAttribute 적용

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

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

  - HelloData 객체를 생성한다 -> 요청 파라미터의 이름으로 HelloData 객체의 프로퍼티를 찾는다 -> 해당 프로퍼티의 setter를 호출해서 파라미터의 값을 입력(바인딩) 한다.

  - 예) 파라미터 이름이 username 이면 setUsername() 메서드를 찾아서 호출하면서 값을 입력한다

 

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

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

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

  > 바인딩 오류

  - age=abc 처럼 숫자가 들어가야 할 곳에 문자를 넣으면 BindException 이 발생한다. 

 

/**
 * @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 생략

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

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

  - String , int , Integer 같은 단순 타입 = @RequestParam

  - 나머지 = @ModelAttribute (argument resolver 로 지정해둔 타입 외)