스프링 MVC(6)
스프링 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가지 방법을 사용한다.
- 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 요청 파라미터 - @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])
- @RequestParam MultivalueMap
- 스프링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