Web/Spring

Spring MVC (6) - 스프링 MVC 기본 기능

now0204 2023. 10. 9. 21:19

1. 들어가기 전에 

 

- 프로젝트 생성 주의점

Packaging War가 아닌, Jar를 선택 -> JSP를 사용하지 않기 때문에 

(스프링 부트를 사용하면 주로 위 방식을 사용하게 된다) 

Jar+내장 톰캣+ webapp경로x

War+ 주로 외부 웹 서버 + webapp경로 o

 

- Welcome페이지 만들기 

스프링 부트에 Jar를 사용하면, /resoureces/static 위치에 파일을 두면, Welcome페이지로 처리해준다.

정적컨텐츠는 주로 저 위치에 두면된다. 

 

1.2 로깅 알아보기 

 

- 운영시스템에서는 System.out.println()같은 시스템 콘솔을 사용해서 필요한 정보 출력x

   > 별도의 로깅 라이브러리를 사용해서 로그를 출력한다.

- 로깅 라이브러리 : 스프링 부트 라이브러리를 사용하면 스프링 부트 로깅 라이브러리가 함께 포함된다. 

                                스프링 부트 로깅 라이브러리는 기본으로 다음 로깅 라이브러리를 사용한다.

                                SLF4J, Logback

- SLF4J: 많은 로그 라이브러리를 통합해서 인터페이스 제공 Logback는 구현체임 (이런 구현체 선택하면됨)

private Logger log = LoggerFactory.getLogger(getClass());
private static final Logger log = LoggerFactory.getLogger(Xxx.class)
//@Slf4j : 롬복 사용 가능
@RestController
public class LogTestController {
 private final Logger log = LoggerFactory.getLogger(getClass());
 @RequestMapping("/log-test")
 public String logTest() {
 String name = "Spring";
 
 log.trace("trace log={}", name);
 log.debug("debug log={}", name);
 log.info(" info log={}", name);
 log.warn(" warn log={}", name);
 log.error("error log={}", name);
 //로그를 사용하지 않아도 a+b 계산 로직이 먼저 실행됨, 이런 방식으로 사용하면 X
 log.debug("String concat log=" + name);
 return "ok";
 }
}

- @RestController는 메서드 String 반환 값을 HTTP 메시지 바디에 바로 입력함 

      *@ResponseBody와 연관이 있다.

 

- 로그 레벨을 설정하여 출력을 하면 

  시간,로그레벨,프로세스 ID, 스레드명, 클래스명, 로그 메시지 쭉 나온다

  TRACE,DEBUG,INFO,WARN,ERROR순서 

  개발 서버는 주로 DEBUG사용, 운영서버는 INFO 사용 

 

*로그 레벨 설정 

application.properties

전체 로그 레벨 설정(기본 info)
logging.level.root=info
#hello.springmvc 패키지와 그 하위 로그 레벨 설정
logging.level.hello.springmvc=debug

- 로그를 찍을 때 +연산말고, {}와 같은 format을 사용하자 -> 의미없는 연산이 발생하지 않음 

- 로그를 사용하면, 쓰레드 정보 클래스 이름 같은 부가 정보 함께 볼 수 있고, 출력 모양 조정 가능 

  로그 레벨에 따라 개발 서버에서는 모든 로그 출력, 운영서버에서는 출력하지 않는 등 상황에 맞게 조절 

  콘솔 뿐 아니라 파일이나 네트워크 등 로그를 별도의 위치에 남길 수 있다. (파일로 남길 시 일별, 용량별 로그 분할 가능) 

 

 

2. 요청 매핑 

 

- 서버는 받을 수 있는 요청들을 미리 지정해두고, 클라이언트는 이에 맞춰서 요청을 날리는 것

  아무 요청이나 날릴 수 있고, 이를 해석해서 서버가 작동하는게 아님! ->

  서버에서 원하는 방향대로만 애초에 요청하도록 만들어야함 -> 

  서버에서 애초에 요청을 날리는 방식을 정해두고, 클라이언트는 이 중 선택

 

2.1 기본 매핑 

@RequestMapping("/hello-basic") // {"/hello-basic", "/hello-go"} 배열 가능
@RequestMapping(value = "/mapping-get-v1", method = RequestMethod.GET)

- 기본 요청 매핑임 HTTP 메서드 모두 허용하는 특징이 있음 

   (GET,HEAD,POST,PUT,PATCH,DELETE)

- 물론 method를 지정할 수 있다.

/**
 * 편리한 축약 애노테이션 (코드보기)
 * @GetMapping
 * @PostMapping
 * @PutMapping
 * @DeleteMapping
 * @PatchMapping
 */
@GetMapping(value = "/mapping-get-v2")

- 특정 HTTP 메서드만 받을 수 있도록 축약형 어노테이션도 제공한다

  어노테이션 내부에보면 @RequestMapping에 method를 지정해서 사용한 것을 볼 수 있다.

 

2.2 PathVariabel(경로 변수) 사용

 

/**
 * PathVariable 사용
 * 변수명이 같으면 생략 가능
 * @PathVariable("userId") String userId -> @PathVariable userId
 */
@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경로를 템플릿({} <- 포맷 )화 할 수 있는데 @PathVaridable을 사용하면,

매칭 되는 부분을 편리하게 조회할 수 있다. 

@GetMapping("/mapping/users/{userId}/orders/{orderId}")
public String mappingPath(@PathVariable String userId, @PathVariable Long orderId){

		// PathVariable 다중 사용

* 쿼리 스트링 날리는 거랑 동일한 효과 다른 방식임!

 

2.3 기본 요청 + 추가 매핑 (파라미터, 헤더 등) 

 

- 특정 파라미터 조건 매핑 

/**
 * 파라미터로 추가 매핑
 * params="mode",
 * params="!mode"
 * params="mode=debug"
 * params="mode!=debug" (! = )
 * params = {"mode=debug","data=good"}
 */
@GetMapping(value = "/mapping-param", params = "mode=debug")

> 특정 파라미터가 있거나 없는 조건을 추가하여 매핑할 수 있음 (잘 사용하지 않음)

 

- 특정 헤더 조건 매핑 

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

> 파라미터 매핑과 비슷 요청 헤더에 특정 값이 있는지 확인 

 

- 미디어 타입 조건 매핑 (Content-Type 헤더 기반 추가 매핑)

/**
 * Content-Type 헤더 기반 추가 매핑 Media Type
 * consumes="application/json"
 * consumes="!application/json"
 * consumes="application/*"
 * consumes="*\/*"
 * MediaType.APPLICATION_JSON_VALUE
 */
@PostMapping(value = "/mapping-consume", consumes = "application/json")
public String mappingConsumes() {
 log.info("mappingConsumes");
 return "ok";
}

* consumes = {"text/plain","application/*"}

consumes = "text/plain"
consumes = {"text/plain", "application/*"}
consumes = MediaType.TEXT_PLAIN_VALUE

- 미디어 타입 조건 매핑 (HTTP 요청 Accept 기반 추가 매핑)

/**
 * Accept 헤더 기반 Media Type
 * produces = "text/html"
 * produces = "!text/html"
 * produces = "text/*"
 * produces = "*\/*"
 */
@PostMapping(value = "/mapping-produce", produces = "text/html")
public String mappingProduces() {
 log.info("mappingProduces");
 return "ok";
}
produces = "text/plain"
produces = {"text/plain", "application/*"}
produces = MediaType.TEXT_PLAIN_VALUE
produces = "text/plain;charset=UTF-8"

 

- Consume : 클라이언트 요청 메시지 타입 중 이것만 받는다  (content-type과 consume 매핑)

- Produce : 서버 응답 결과는 이런 타입이다 (요청 메시지에 클라이언트 accept와 produce 매핑) 

 

2.4 요청 매핑 - API 예시 

@RestController
@RequestMapping("/mapping/users")
public class MappingClassController {
 /**
 * GET /mapping/users
 */
 @GetMapping
 public String users() {
 return "get users";
 }
 /**
 * POST /mapping/users
 */
 @PostMapping
 public String addUser() {
 return "post user";
 }
 /**
 * GET /mapping/users/{userId}
 */
 @GetMapping("/{userId}")
 public String findUser(@PathVariable String userId) {
 return "get userId=" + userId;
 }
 /**
 * PATCH /mapping/users/{userId}
 */
 @PatchMapping("/{userId}")
 public String updateUser(@PathVariable String userId) {
 return "update userId=" + userId;
 }
 /**
 * DELETE /mapping/users/{userId}
 */
 @DeleteMapping("/{userId}")
 public String deleteUser(@PathVariable String userId) {
 return "delete userId=" + userId;
 }
}
회원 관리 API
회원 목록 조회: GET /users
회원 등록: POST /users
회원 조회: GET /users/{userId}
회원 수정: PATCH /users/{userId}
회원 삭제: DELETE /users/{userId}

 

3. HTTP 요청 파라미터 (데이터 조회) 

 

3.1 기본/헤더  

 

> 애노테이션 기반의 스프링 컨트롤러는 다양한 파라미터를 지원함 

> 헤더정보를 조회하는 방법을 알아보자 

 

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

 - Request,Response

 - HttpMethod : HTTP 메서드 조회 , Locale : Locale 정보 조회 

 - @RequestHeader MultiValueMap<String,String> headerMap : 모든 헤더 조회

- @RequestHeader("host") String host : 특정 헤더 값 조회 

      required와 defaultValue 설정 가능 

- @CookieValue: 특정 쿠기 조회 기능 

 

* 지원가능한 파라미터 적으면, 핸들러 어답터가 처리해줌! (기본은 HTTP 요청메시지)

* MultiValueMap : MAP과 유사한데, 하나의 키에 여러 값을 받을 수 있다.

  HTTP header, HTTP 쿼리 파라미터 같이 하나의 키에 여러 값을 받을 때 사용

  keyA=value1&keyA=value2 

*Controller에 사용 가능한 파라미터 + 응답 가능한 값은 공식 메뉴얼에서 확인 가능 

 

 3.2 쿼리 파라미터, HTML Form 

 

GET 쿼리 파라미터 전송 방식/ POST(HTML Form) 방식은 둘다 형식이 같아서 구분 없이 조회 가능 

-> 간단히 요청 파라미터 조회라고 함 

 

- request.getParameter()

/**
 * 반환 타입이 없으면서 이렇게 응답에 값을 직접 집어넣으면, view 조회X
 */
 @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"));
 //
 }

-> 서블릿 방식 request.getParameter()사용해서 조회 가능 

 

- @RequestParam

/**
 * @RequestParam 사용
 * - 파라미터 이름으로 바인딩
 * @ResponseBody 추가
 * - View 조회를 무시하고, HTTP message body에 직접 해당 내용 입력
 */
@ResponseBody
@RequestMapping("/request-param-v2")
public String requestParamV2(
 @RequestParam("username") String memberName,
 @RequestParam("age") int memberAge) {
 //
 }
 
 /**
 * @RequestParam 사용
 * HTTP 파라미터 이름이 변수 이름과 같으면 @RequestParam(name="xx") 생략 가능
 */
@ResponseBody
@RequestMapping("/request-param-v3")
public String requestParamV3(
 @RequestParam String username,
 @RequestParam int age)
 
 /**
 * @RequestParam 사용
 * String, int 등의 단순 타입이면 @RequestParam 도 생략 가능
 */
@ResponseBody
@RequestMapping("/request-param-v4")
public String requestParamV4(String username, int age)

> @RequestParam : 파라미터 이름 바인딩 (request.getParameter와 같음)

> @ResponseBody : String return값 message body에 바로 넣기

 

*@RequestParam을 생략하면, 스프링 MVC 내부에서 required=false를 적용함 

  기본형일 경우 애노테이션 완전 생략 가능한데 그냥 적어주는게 나음 

 

- 파라미터 필수 여부 및 기본값 

/**
 * @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)
 
 
 /**
 * @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)

 > 파라미터 필수 여부 지정 가능 (기본값은 true)

 > 필수 파라미터를 지정하지 않으면, 400예외 발생 

 > 파라미터 이름만 사용 (/param?username=) 

    - 이럴 경우 빈 문자로 통과함 

 > 기본형에 null 입력 (@RequestParam(required=false) int age)

     - int형에 null 입력 불가능 (500예외)

    - Integer로 타입 변경 혹은 defaultValue사용하자 

 > 기본 값이 있는 경우 사실 required는 딱히 의미가 없긴함!

 

- 파라미터 Map으로 조회하기 (requestParam Map<>)

/**
 * @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으로 반환 받는다.

 

3.3 요청 파라미터 to 객체

 

- 실제 개발을 하면 요청 파라미터들을 통해 필요한 객체를 만들고 값을 넣어줌 

- 이를 쉽게 만들어주는 어노테이션이 있음 

 

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

> 요청파라미터 값 담을 객체 (@Data하면 롬복이 Getter,Setter,ToString 등등 다해줌)

/**
 * @ModelAttribute 사용
 * 참고: model.addAttribute(helloData) 코드도 함께 자동 적용됨, 뒤에 model을 설명할 때
자세히 설명
 */
@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData)

- 스프링 MVC는 @ModelAttribute가 있으면 요청 파라미터 이름과 객체의 프로퍼티 찾음

- 해당 프로퍼티의 setter를 호출해서 파라미터 값을 바인딩한다.

 

* 프로퍼티 

  - 객체에 getUsername(),setUsername()메서드가 있으면, 객체는 username이라는 프로퍼티 가짐 

* 바인딩 오류 

   age=abc처럼 숫자 들어가야할 자리에 문자 넣으면 BindException이 발생 (처리 방법은 검증에서 다룸)

 

* @ModelAttribute 생략 가능
 * String, int 같은 단순 타입 = @RequestParam
 * argument resolver 로 지정해둔 타입 외 = @ModelAttribute
 */
@ResponseBody
@RequestMapping("/model-attribute-v2")
public String modelAttributeV2(HelloData helloData)

- @ModelAttribute 생략 가능 (@RequestParam도 생략 가능)

   > 단순타입은 RequestParam적용, 나머지는 ModelAttribute적용 

  

*argument resolver는 뒤에서 학습 

 

*여기까지 get과 html form데이터 조회 방법 알아봄 

  크게 @RequestParam(데이터 조회)

          @ModelAttribute(데이터 to 객체) 

  필수 값이나 기본 값 설정 등을 다룸

 

 

 4. HTTP 요청 메시지 - HTTP API

 

- HTTP message body에 데이터 직접 담아서 요청 

  > HTTP API에서 주로 사용 JSON,XML,TEXT (주로 JSON)

 

* 요청 파라미터와 다르게, HTTP 메시지 바디를 통해 데이터가 직접 넘어오는 경우

@RequestParam, @ModelAttribute 사용 못함! 

 

4.1 단순 텍스트 

 

- 기존 서블릿 방식 

@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);

 

  > request를 통해 InputStream을 열고, StreamUtils를 통해 String으로 만듦

 

- Input/OutputStream 파라미터

/**
 * InputStream(Reader): HTTP 요청 메시지 바디의 내용을 직접 조회
 * OutputStream(Writer): HTTP 응답 메시지의 바디에 직접 결과 출력
 */
@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");
}

> 파라미터로 inputStream과 Writer 등도 쓸 수 있음! (요청 파라미터 말고, body 읽기 용도)

  InputStream(Reader) : HTTP 요청 메시지 바디의 내용 직접 조회

  OutputStream(Writer) : HTTP 응답 메시지의 바디에 직접 결과 출력

 

- HttpEntity 방식 

/**
 * HttpEntity: HTTP header, body 정보를 편리하게 조회
 * - 메시지 바디 정보를 직접 조회(@RequestParam X, @ModelAttribute X)
 * - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
 *
 * 응답에서도 HttpEntity 사용 가능
 * - 메시지 바디 정보 직접 반환(view 조회X)
 * - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
 */
@PostMapping("/request-body-string-v3")
public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) {
 String messageBody = httpEntity.getBody();
 log.info("messageBody={}", messageBody);
 return new HttpEntity<>("ok");
}

> HttpEntity: HTTP header, body내용 편리하게 조회하는 기능함 

                        - 메시지 바디 직접 조회

                        - 요청 파라미터 조회 기능과는 관계 없다.

                        - request에서 헤더 조회기능과, InputStream얻어서 바디 조회 기능만 가짐 

                        - 응답(return값)으로 사용할 수도 있다. 

                        - 이때 헤더 정보 포함 가능하고, view조회 안하고 응답 메시지 대신 HttpEntity를 준다.

 

> RequestEntity: HttpMehod,url정보 추가 (요청에서 사용)

> ResponseEntity : HTTP 상태코드 설정 가능, 응답서 사용

return new ResponseEntity<String>("Hello World", responseHeaders, 
HttpStatus.CREATED)

* 스프링 내부에서 HTTP 메시지 바디를 읽어서 문자나 객체로 변환해서 전달해 줌 

  이때 HTTP 메시지 컨버터라는 기능을 사용

 

- @RequestBody 방식 

/**
 * @RequestBody
 * - 메시지 바디 정보를 직접 조회(@RequestParam X, @ModelAttribute X)
 * - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
 *
 * @ResponseBody
 * - 메시지 바디 정보 직접 반환(view 조회X)
 * - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
 */
@ResponseBody
@PostMapping("/request-body-string-v4")
public String requestBodyStringV4(@RequestBody String messageBody) {
 log.info("messageBody={}", messageBody);
 return "ok";
}

- @RequestBody를 사용하면, HTTP 바디 정보 편리하게 조회 가능하다 

   헤더 정보가 필요하면 HttpEntity를 사용하거나, @RequestHeader를 사용하자 

 

4.2 JSON

 

- HTTP API에서 주로 사용되는 JSON 형식을 조회해보자 

 

- 기존 방식 

public class RequestBodyJsonController {
 private ObjectMapper objectMapper = new ObjectMapper();
 @PostMapping("/request-body-json-v1")
 public void requestBodyJsonV1(HttpServletRequest request,
HttpServletResponse response) throws IOException {
 ServletInputStream inputStream = request.getInputStream();
 String messageBody = StreamUtils.copyToString(inputStream,
StandardCharsets.UTF_8);
 log.info("messageBody={}", messageBody);
 HelloData data = objectMapper.readValue(messageBody, HelloData.class);
 log.info("username={}, age={}", data.getUsername(), data.getAge());
 response.getWriter().write("ok");
 }
}

> 읽는 방식 동일, ObjectMapper로 Body의 내용을 특정 클래스로 변경 (readValue)

 

- @RequestBody사용 

/**
 * @RequestBody
 * HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
 *
 * @ResponseBody
 * - 모든 메서드에 @ResponseBody 적용
 * - 메시지 바디 정보 직접 반환(view 조회X)
 * - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
 */
@ResponseBody
@PostMapping("/request-body-json-v2")
public String requestBodyJsonV2(@RequestBody String messageBody) throws
IOException {
 HelloData data = objectMapper.readValue(messageBody, HelloData.class);
 log.info("username={}, age={}", data.getUsername(), data.getAge());
 return "ok";
}

> HTTP 바디 내용을 읽는 부분을 줄일 수 있음! 

> 여전히 objectMapper를 사용해서 자바 객체로 변환 

> 문자 to json 변환 과정에서 @ModelAttribute와 같이 한번에 객체로 변환 안되나?

 

- @RequestBody + 객체 

/**
 * @RequestBody 생략 불가능(@ModelAttribute 가 적용되어 버림)
 * HttpMessageConverter 사용 -> MappingJackson2HttpMessageConverter (contenttype: application/json)
 *
 */
@ResponseBody
@PostMapping("/request-body-json-v3")
public String requestBodyJsonV3(@RequestBody HelloData data)

> @RequestBody생략 없이 String이 아닌, 객체를 넣으면 변환해준다.

> HttpEntity<특정클래스>, @RequestBody를 사용하면, HTTP 메시지 컨버터가 HTTP 메시지 바디의 내용을 

우리가 원하는 문자나 객체로 변환해 준다.

> @RequestBody는 생략 불가능이다. -> 생략하면 요청 파라미터로 처리함

 

* HTTP 요청시에 content-type이 application/json인지 꼭 확인해야함 그래야 json으로 처리해주는 메시지 컨버터가 실행된다.

 

- ResponseBody

/**
 * @RequestBody 생략 불가능(@ModelAttribute 가 적용되어 버림)
 * HttpMessageConverter 사용 -> MappingJackson2HttpMessageConverter (contenttype: application/json)
 *
 * @ResponseBody 적용
 * - 메시지 바디 정보 직접 반환(view 조회X)
 * - HttpMessageConverter 사용 -> MappingJackson2HttpMessageConverter 적용
(Accept: application/json)
 */
@ResponseBody
@PostMapping("/request-body-json-v5")
public HelloData requestBodyJsonV5(@RequestBody HelloData data) {
 log.info("username={}, age={}", data.getUsername(), data.getAge());
 return data;
}

> 응답에도 객체를 집어 넣을 수 있다! (HttpEntity도 사용가능)

>  메시지 컨버터가 객체를 JSON으로 바꿔서 응답해준다.