스프링 MVC(6)

5 minute read

스프링 MVC 기본 기능 - 요청, 기본 헤더 조회

@RestController
public class MappingController {
    private Logger log = LoggerFactory.getLogger(getClass());

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

    @GetMapping("/mapping/{userId}")
    public String mappingPath(@PathVariable("userId") String data){
        log.info("mappingPath userId={}",data);
        return "ok";
    }
}
  • @RequestMapping()
    • /hello-basic URL 호출이 오면 이 메서드가 실행되도록 매핑
    • 대부분의 속성을 배열로 제공하므로 다중 URL 설정이 가능하다. @RequestMapping(“/hello-basic”,”/hello”)
    • @RequestMapping에 method 속성으로 HTTP 메서드를 지정하지 않으면 HTTP 메서드와 무관하게 호출(Get, HEAD, POST, PUT, PATCH, DELETE 모두 허용)
  • @GetMapping에서 만약에 POST 요청을 하면 스프링 MVC는 HTTP 405 상태코드(Method Not Allowed)를 반환

PathVariable(경로 변수)

    @GetMapping("/mapping/{userId}")
    public String mappingPath(@PathVariable("userId") String data){
        log.info("mappingPath userId={}",data);
        return "ok";
    }
  • 최근 HTTP API는 다음과 같이 리소스 경로에 식별자를 넣는 스타일을 선호한다.
    • /mapping/userA
    • /users/1
  • @RequestMapping은 URL 경로를 템플릿화 할 수 있는데, @PathVariable을 사용하면 매칭 되는 부분을 편리하게 조회할 수 있다
  • @PathVariable의 이름과 파라미터 이름이 같으면 생략가능
    • @GetMapping(“/mapping/{userId}) -> public String mappingPath(@PathVariable String userId)

특정 조건 매핑

  • 특정 파라미터 조건 매핑
@GetMapping(value = "/mapping-param", params = "mode=debug")
public String mappingParam() {
    log.info("mappingParam");
    return "ok";
}

-> 특정 파라미터가 있거나 없는 조건을 추가할 수 있지만 잘 사용하지 않음

  • 특정 헤더 조건 매핑
@GetMapping(value = "/mapping-header", headers = "mode=debug")
public String mappingHeader() {
    log.info("mappingHeader");
    return "ok";
}

-> HTTP 헤더를 사용

  • 미디어 타입 조건 매핑 : HTTP 요청 Content-Type,consume
@PostMapping(value = "/mapping-consume", consumes = "application/json")
public String mappingConsumes() {
    log.info("mappingConsumes");
    return "ok";
}

-> 맞지 않으면 HTTP 415 상태코드(Unsupported Media Type)을 반환

  • 미디어 타입 조건 매핑 : HTTP 요청 Accept, produce
@PostMapping(value = "/mapping-produce", produces = "text/html")
public String mappingProduces() {
    log.info("mappingProduces");
    return "ok";
}

-> 만약 맞지 않으면 HTTP 406 상태코드(Not Acceptable)을 반환

참고

  • consumes는 서버가 받을 수 있는 데이터 형식이고, produces는 클라이언트가 받을 수 있는 데이터 형식이다.

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

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

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

HTTP 요청 파라미터 - @RequestParam

@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"));
        response.getWriter().write("ok");
    }

    @ResponseBody
    @RequestMapping("/request-param-v2")
    public String requestParamV2(@RequestParam("username") String memberName,
                                 @RequestParam("age") int memberAge){
        return "ok";
    }

    @ResponseBody
    @RequestMapping("/request-param-v3")
    public String requestParamV3(@RequestParam String username,
                                 @RequestParam int age){
        return "ok";
    }

    @ResponseBody
    @RequestMapping("/request-param-v4")
    // HTTP 파라미터 이름이 변수 이름과 같으면 @RequestParam 생략가능
    public String requestParamV4(String username, int age){
        return "ok";
    }

    @ResponseBody
    @RequestMapping("/request-param-v5")
    //String 빈 문자 통과되기 때문에 주의
    //age는 값을 안받아도 된다면 Integer를 사용해야한다 int 형에는 null이 들어갈 수 없기 때문이다.
    public String requestParamV5(@RequestParam(required = true) String username,@RequestParam(required = false) Integer age){
        return "ok";
    }

    @ResponseBody
    @RequestMapping("/request-param-default")
    //String 빈 문자 통과되기 때문에 주의
    //age는 값을 안받아도 된다면 Integer를 사용해야한다 int 형에는 null이 들어갈 수 없기 때문이다.
    public String requestParamDefault(@RequestParam(required = true,defaultValue = "guest") String username,@RequestParam(required = false,defaultValue = "-1") Integer age){
        return "ok";
    }

    @ResponseBody
    @RequestMapping("/request-param-map")
    //String 빈 문자 통과되기 때문에 주의
    //age는 값을 안받아도 된다면 Integer를 사용해야한다 int 형에는 null이 들어갈 수 없기 때문이다.
    public String requestParamMap(@RequestParam Map<String,Object> paramMap){
        log.info("username={}",paramMap.get("username"));
        return "ok";
    }

    @ResponseBody
    @RequestMapping("/model-attribute-v1")
    //@ModelAttribute도 생략가능
    public String modelAttributeV1(@ModelAttribute HelloData helloData){
        log.info("username={}",helloData.getUsername());
        return "ok";
    }
}
//@Data는 @Getter, @Setter, @ToString, @EqualsAndHashCode, @RequiredArgsConstructor 를 자동으로 적용
@Data
public class HelloData {
    private String username;
    private int age;
}
  • 파라미터의 이름으로 전돨되는 값이 2개 이상이라면 MultiValueMap 사용가능
    • @RequestParam MultivalueMap
      • MultiValueMap(key=[value1, value2, …] ex) (key=userIds, value=[id1, id2])
  • 스프링MVC는 @ModelAttribute가 있으면 다음을 실행
    • HelloData 객체를 생성
    • 요청 파라미터의 이름으로 HelloData 객체의 프로퍼티를 찾는다. 그리고 해당 프로퍼티의 setter를 호출해서 파라미터의 값을 입력(바인딩) 한다.
    • 예) 파라미터 이름이 username 이면 setUsername() 메서드를 찾아서 호출하면서 값을 입력한다.
    • 프로퍼티 : 객체에 getUsername() , setUsername() 메서드가 있으면, 이 객체는 username 이라는 프로퍼티를 가지고 있다.
    • 바인딩 오류 : age=abc 처럼 숫자가 들어가야 할 곳에 문자를 넣으면 BindException 이 발생한다.
  • @ModelAttribute와 @RequestParam도 생략할 수 있다 혼란이 발생할 수 있는데 다음과 같은 규칙을 적용한다.
    • String , int , Integer 같은 단순 타입 = @RequestParam
    • 나머지 = @ModelAttribute (argument resolver 로 지정해둔 타입 외)

HTTP 요청 메시지 - 단순 텍스트

요청 파라미터와 다르게, HTTP 메시지 바디를 통해 데이터가 직접 넘어오는 경우는 @RequestParam ,@ModelAttribute 를 사용할 수 없다. (물론 HTML Form 형식으로 전달되는 경우는 요청 파라미터로 인정된다.)

public class RequestBodyStringController {

    //InputStream을 사용해서 직접읽기
    @PostMapping("/request-body-string-v1")
    public void requestBodyString(HttpServletRequest request, HttpServletResponse response) throws IOException {
        ServletInputStream inputStream = request.getInputStream();
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
        log.info("messagebody={}",messageBody);

    }

    @PostMapping("/request-body-string-v2")
    public void requestBodyStringV2(InputStream inputStream, Writer responseWriter) throws IOException {
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
        log.info("messagebody={}",messageBody);
        responseWriter.write("ok");
    }

    @PostMapping("/request-body-string-v3")
    public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) throws IOException {
        String body = httpEntity.getBody();
        log.info("messagebody={}",body);
        return new HttpEntity<>("ok");

    }

    @ResponseBody
    @PostMapping("/request-body-string-v4")
    public String requestBodyStringV4(@RequestBody String messageBody) throws IOException {
        return "ok";

    }
}
  • InputStream(Reader): HTTP 요청 메시지 바디의 내용을 직접 조회
  • OutputStream(Writer): HTTP 응답 메시지의 바디에 직접 결과 출력
  • HttpEntity: HTTP header, body 정보를 편리하게 조회
    • 메시지 바디 정보를 직접 조회
    • 요청 파라미터를 조회하는 기능과 관계 없음 @RequestParam X, @ModelAttribute X
  • HttpEntity는 응답에도 사용 가능
    • 메시지 바디 정보 직접 반환
    • 헤더 정보 포함 가능
    • view 조회X
    • HttpEntity를 상속받은 다음 객체들도 같은 기능 제공
      • RequestEntity : HttpMethod, url 정보가 추가, 요청에서 사용
      • ResponseEntity : HTTP 상태 코드 설정 가능, 응답에서 사용, return new ResponseEntity("Hello World", responseHeaders, HttpStatus.CREATED)
  • @RequestBody : @RequestBody 를 사용하면 HTTP 메시지 바디 정보를 편리하게 조회할 수 있다. 참고로 헤더 정보가 필요하다면 HttpEntity 를 사용하거나 @RequestHeader 를 사용하면 된다. 이렇게 메시지 바디를 직접 조회하는 기능은 요청 파라미터를 조회하는 @RequestParam , @ModelAttribute 와는 전혀 관계가 없다.
  • @ResponseBody : @ResponseBody 를 사용하면 응답 결과를 HTTP 메시지 바디에 직접 담아서 전달할 수 있다. 물론 이 경우에도 view를 사용하지 않는다.

요청 파라미터 vs HTTP 메시지 바디

  • 요청 파라미터를 조회하는 기능: @RequestParam , @ModelAttribute
  • HTTP 메시지 바디를 직접 조회하는 기능: @RequestBody

HTTP 요청 메시지 - JSON

@Slf4j
@Controller
public class ResponseBodyController {

    @GetMapping("/response-body-string-v1")
    public void responseBodyV1(HttpServletResponse response) throws IOException {
        response.getWriter().write("ok");
    }

    @GetMapping("/response-body-string-v2")
    public ResponseEntity<String> responseBodyV2(){
       return new ResponseEntity<>("ok", HttpStatus.OK);
    }

    @ResponseBody
    @GetMapping("/response-body-string-v3")
    public String responseBodyV3(){
        return "ok";
    }

    @GetMapping("/response-body-json-v1")
    public ResponseEntity<HelloData> responseBodyJsonV1(){
        HelloData helloData = new HelloData();
        helloData.setUsername("changmin");
        helloData.setAge(20);
        return new ResponseEntity<>(helloData,HttpStatus.OK);
    }

    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    @GetMapping("/response-body-json-v2")
    public HelloData responseBodyJsonV2(){
        HelloData helloData = new HelloData();
        helloData.setUsername("changmin");
        helloData.setAge(20);
        return helloData;
    }
}
  • @RequestBody는 생략 불가능
    • 생략한다면 @ModelAttribute나 @RequestParam 둘 중하나로 인식함
  • @RequestBody 요청 : JSON 요청 -> HTTP 메시지 컨버터 -> 객체
  • @ResponseBody 응답 : 객체 -> HTTP 메시지 컨버터 -> JSON 응답

출처 - 김영한의 스프링 MVC 1편

Leave a comment