[스프링 MVC 1편] HTTP 요청 매핑, 요청 파라미터

강신현·2022년 9월 19일
0

✅ @Controller ✅ @RestController ✅ RequestMapping ✅ @RequestParam ✅ @ModelAttribute


@RestController
public class MappingController {

    private Logger log = LoggerFactory.getLogger(getClass());

    @RequestMapping("/hello-basic")
    public String helloBasic(){
        log.info("helloBasic");
        return "ok";
    }
}

📕 @Controller vs @RestController

@Controller

@Controller 는 반환 값이 String 이면 뷰 이름으로 인식된다. 그래서 뷰를 찾고 뷰가 랜더링 된다.

@Conroller 의 사용 가능한 파라미터 목록
@Conroller 의 사용 가능한 응답 값 목록

@RestController

@RestController 는 반환 값으로 뷰를 찾는 것이 아니라, HTTP 메시지 바디에 바로 입력한다.
따라서 실행 결과로 ok 메세지를 받을 수 있다.


💡 @RequestMapping("")

" " URL 호출이 오면 이 메서드가 실행되도록 매핑한다.

  • 대부분의 속성을 배열[] 로 제공하므로 다중 설정이 가능하다. {"/hello-basic", "/hello-go"}
  • 스프링은 다음 URL 요청들을 같은 요청으로 매핑한다.
    • 매핑: /hello-basic
    • URL 요청: /hello-basic , /hello-basic/
  • method 속성으로 HTTP 메서드 (GET, HEAD, POST, PUT, PATCH, DELETE)를 지정할 수 있다.
    • 지정하지 않으면 HTTP 메서드와 무관하게 호출된다. (모두 허용)
    • HTTP 메서드를 축약한 애노테이션을 사용하는 것이 더 직관적이다.

- @GetMapping

- @PostMapping

- @PutMapping

- @DeleteMapping

- @PatchMapping

@GetMapping(value = "/mapping-get-v2")
public String mappingGetV2() {
    log.info("mapping-get-v2");
    return "ok";
}

💡 PathVariable(경로 변수)

  • 최근 HTTP API는 QueryParam 방법보다 PathVariable로 리소스 경로에 식별자를 넣는 스타일을 선호한다.
  • @PathVariable 의 이름과 파라미터 이름이 같으면 생략할 수 있다. (@PathVariable String data)
@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable("userId") String data) {
    log.info("mappingPath userId={}", data);
    return "ok";
}

만약 어떤 resource를 식별하고 싶으면 Path Variable을 사용하고,
정렬이나 필터링을 한다면 Query Parameter를 사용하는 것이 Best Practice이다.


📕 헤더 정보 조회

@Slf4j
@RestController
public class RequestHeaderController {

    @RequestMapping("/headers")
    public String headers(HttpServletRequest request,
                          HttpServletResponse response,
                          HttpMethod httpMethod,
                          Locale locale,
                          @RequestHeader MultiValueMap<String, String> headerMap,
                          @RequestHeader("host") String host,
                          @CookieValue(value = "myCookie", required = false) String cookie
                          ){

        log.info("request={}", request);
        log.info("response={}", response);
        log.info("httpMethod={}", httpMethod);
        log.info("locale={}", locale);
        log.info("headerMap={}", headerMap);
        log.info("header host={}", host);
        log.info("myCookie={}", cookie);

        return "ok";
    }
}

📕 요청 파라미터

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 message body에 데이터를 직접 담아서 요청

  • HTTP API에서 주로 사용, JSON, XML, TEXT
  • 데이터 형식은 주로 JSON 사용
  • POST, PUT, PATCH

1. 💡 쿼리 파라미터, HTML Form

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

2. 💡 @RequestParam

@ResponseBody // View 조회를 무시하고, HTTP message body에 직접 해당 내용 입력 👉 RessController와 같은 효과
@RequestMapping("/request-param-v2")
public String requestParamV2(
        @RequestParam("username") String memberName,
        @RequestParam("age") int memberAge) {

    log.info("username={}, age={}", memberName, memberAge);
    return "ok";
}

- 변수명 생략

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

- @RequestParam 생략

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

  • @RequestParam 애노테이션을 생략하면 스프링 MVC는 내부에서 required=false 를 적용한다.
    @RequestParam.required : 파라미터 필수 여부
    기본값이 파라미터 필수 (required=true)이다.
  • 강사님은 이렇게 애노테이션을 완전히 생략해도 되는데, 너무 없는 것도 약간 과하다고 생각하신다고 함.
@ResponseBody
@RequestMapping("/request-param-v4")
public String requestParamV4(String username, int age) {

    log.info("username={}, age={}", username, age);
    return "ok";
}

- 파라미터를 Map으로 조회

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

주의할 점

1) 파라미터 이름만 사용한 경우

  • /request-param?username=

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

2) 기본형(primitive)에 null을 입력한 경우

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

👉 null 을 int 에 입력하는 것은 불가능(500 예외 발생)
👉 따라서 null 을 받을 수 있는 Integer 로 변경하거나, defaultValue 옵션을 사용해야 함


3. 💡 @ModelAttribute

요청 파라미터를 받아서 필요한 객체를 만들고 그 객체에 값을 넣어주는 과정을 완전히 자동화해주는 기능

@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData) {
    log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
    return "ok";
}
  • @ModelAttribute 는 생략할 수 있다.
  • 하지만 @ModelAttribute 가 생략된건지, @RequestParam 가 생략된건지 혼란이 발생할 수 있다.
  • 따라서 스프링은 해당 생략시 다음과 같은 규칙을 적용한다.
    • @RequestParam : String , int , Integer 같은 단순 타입
    • @ModelAttribute : 나머지 (HttpServletRequest 같이 argument resolver 로 지정해둔 타입 외)

- vs @RequestBody

@ModelAttribute

  • 클라이언트가 전송하는 HTTP parameter(URL 끝에 추가하는 파라미터), HTTP Body 내용을 Setter 함수를 통해 1:1로 객체에 데이터를 바인딩한다. (Setter 필수!)
  • HTTP Body 내용은 multipart/form-data 형태

@RequestBody

  • 요청 본문의 body에 json이나 xml값으로 요청을 하여 HttpMessageConveter를 반드시 거쳐 DTO객체에 맞는 타입으로 바꿔서 바인딩을 시켜준다.
  • 클라이언트가 body에 application/json 형태로 값(보통 객체)을 담아 전송

👉 json에 담기는 속성들로만 이루어져 있다면 @RequestBody 사용하면 되고,
json에 담기지 않는 이미지 파일 같은 MultipartFile을 dto에 같이 사용해야 한다면 @ModelAttribute을 사용한다.

실제 프로젝트에서 사용한 예시 코드

  • dto
@NoArgsConstructor
@AllArgsConstructor
@Data
public class createBoardReq {

    private String title;
    private String content;
    private List<MultipartFile> photos;
    private Long userId;
    private CategoryName categoryName1;
    private CategoryName categoryName2;
    private CategoryName categoryName3;

    public Board toEntityBoard(User user) {
        return new Board(user, this.title, this.content, LocalDateTime.now());
    }

    public Category toEntityCategory(CategoryName categoryName){
        return Category.builder()
                .categoryName(categoryName)
                .build();
    }
}
  • controller
/**
* 게시글 등록
* @author 강신현
*/
@ApiOperation(value = "게시글 등록", notes = "swagger에서 이미지 여러장 업로드 시, 에러가 있으므로 postman에서 테스트 해주세요 (https://www.postman.com/solar-desert-882435/workspace/plogging/request/18177198-883b3d8e-82a7-4228-8bb6-c076ad75749a)")
@PostMapping("")
public ApplicationResponse<BoardRes> boardCreate(@ModelAttribute createBoardReq createBoardReq){
  return boardService.createBoard(createBoardReq);
}

강의 출처

[인프런 - 김영한] 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술

profile
땅콩의 모험 (server)

0개의 댓글