일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 자바의 정석 기초편 ch1
- 자바의 정석 기초편 ch4
- 자바의 정석 기초편 ch14
- 스프링 입문(무료)
- 스프링 mvc2 - 타임리프
- 스프링 mvc1 - 스프링 mvc
- 2024 정보처리기사 시나공 필기
- @Aspect
- 코드로 시작하는 자바 첫걸음
- 자바의 정석 기초편 ch3
- 자바의 정석 기초편 ch7
- 스프링 고급 - 스프링 aop
- jpa 활용2 - api 개발 고급
- 자바의 정석 기초편 ch13
- 자바의 정석 기초편 ch11
- 자바의 정석 기초편 ch9
- 자바의 정석 기초편 ch12
- 자바의 정석 기초편 ch2
- 게시글 목록 api
- jpa - 객체지향 쿼리 언어
- 자바의 정석 기초편 ch8
- 스프링 mvc2 - 검증
- 스프링 mvc2 - 로그인 처리
- 스프링 mvc1 - 서블릿
- 타임리프 - 기본기능
- 자바의 정석 기초편 ch6
- 스프링 db1 - 스프링과 문제 해결
- 2024 정보처리기사 수제비 실기
- 스프링 db2 - 데이터 접근 기술
- 자바의 정석 기초편 ch5
- Today
- Total
나구리의 개발공부기록
스프링 MVC - 기본기능, HTTP요청 (기본/헤더 조회), HTTP 요청 파라미터(쿼리파라미터/HTML Form/@RequestParam/@ModelAttribute), HTTP 요청메시지(단순텍스트/JSON) 본문
스프링 MVC - 기본기능, HTTP요청 (기본/헤더 조회), HTTP 요청 파라미터(쿼리파라미터/HTML Form/@RequestParam/@ModelAttribute), HTTP 요청메시지(단순텍스트/JSON)
소소한나구리 2024. 3. 3. 20:42 출처 : 인프런 - 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 (유료) / 김영한님
유료 강의이므로 정리에 초점을 두고 코드는 일부만 인용
1. HTTP 요청 - 기본, 헤더 조회
1) HTTP 헤더 정보 조회
(1) RequestHeaderController
- basic하위에 request패키지를 만들어서 작성
- HttpMethod: HTTP 메서드를 조회
- Locale: Locale 정보를 조회
- @RequestHeader MultiValueMap<String, String> headerMap: 모든 HTTP헤더를 MultiValueMap형식으로 조회
- @RequestHeader("host") String host: 특정 HTTP 헤더를 조회
- @CookieValue(value = "myCookie", required = false) String cookie: 특정 쿠키를 조회
- @RequestHeader와 @CookieValue에는 required옵션으로 필수 값 여부를 줄 수 있으며 기본 값은 defaultValue임
package hello.springmvc.basic.request;
@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("host={}", host);
log.info("cookie={}", cookie);
return "ok";
}
}
(2) MultiValueMap
- MAP과 유사한데, 하나의 Key에 여러 값을 받을 수 있음
- HTTP header, HTTP쿼리 파라미터와 같이 하나의 키에 여러 값을 받을 때 사용 (keyA=value1&keyA=value2)
- 반환시 배열로 반환
MultiValueMap<String, String> map = new LinkedMultiValueMap();
map.add("keyA", "value1");
map.add("keyA", "value2");
//[value1, value2]
List<String> values = map.get("keyA");
** 참고
2. HTTP 요청 파라미터 - 쿼리 파라미터, HTML Form
1) HTTP 요청 데이터 조회
(1) 클라이언트에서 서버로 요청 데이터를 전달할 때 주로 사용하는 3가지 방식
- GET - 쿼리 파라미터: 메시지 바디 없이 URL의 쿼리 파라미터에 데이터를 포함해서 전달
- POST - HTML Form: 메시지 바디에 쿼리 파라미터 형식으로 전달
- POST,PUT,PATCH - HTTP message body에 데이터를 직접 담아 요청: HTTP API에서 주로 사용하고 데이터 형식은 주로 JSON을 사용
(2) 요청 파라미터 - 쿼리 파라미터, HTML Form
- HttpServletRequest의 request.getParameter()를 사용하면 GET, 쿼리 파라미터 전송 방식과 POST, HTML Form 요청 파라미터를 조회할 수 있음
- GET 쿼리 파라미터 전송 방식과 POST HTML Form 전송 방식이 둘다 형식이 같아서 구분없이 조회가 가능한데 이것을 간단히 요청 파라미터 조회(request parameter)라고 함
(3) RequestParamController
- 스프링으로 요청 파라미터를 조회하는 컨트롤러
- requestParamV1 메서드에서는 단순히 HttpServletRequest가 제공하는 방식으로 요청 파라미터를 조회함
package hello.springmvc.basic.request;
@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");
}
}
(4) Post Form 페이지 생성, hello-form.html
- 테스트용 HTML Form
- resources/static/basic 경로에 hello.form.html을 생성
- 애플리케이션을 실행하여 localhost:8080/basic/hello-form.html로 접속하면 해당 입력 폼이 뜸
<!DOCTYPE html>
<html lang="en">
<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>
** 참고
- Jar를 사용하면 webapp 경로를 사용할 수 없으므로 정적 리소스는 /resources/static 아래에 두면 스프링 부트가 자동으로 인식함
3. HTTP 요청 파라미터 - @RequestParam
1) @RequestParam
(1) requestParamV2 - @RequestParam 적용
- @RequestParam : 파라미터 이름으로 바인딩, request.getParameter()와 같은 효과
- @ResponseBody : View 조회를 무시하고 HTTP message body에 직접 해당 내용을 입력
- @RequestParam의 ("value") 속성이 파라미터 이름으로 사용됨
@ResponseBody // RestController 효과, 뷰이름 조회 없이 응답 메시지를 바로 반환
@RequestMapping("/request-param-v2")
public String requestParamV2(@RequestParam("username") String memberName,
@RequestParam("age") int memberAge) {
log.info("username={}, age={}", memberName, memberAge);
return "ok";
}
(2) requestParamV3 - 파라미터이름생략
- HTTP 파라미터 이름이 변수 이름과 같으면 @RequestParam(name = "xxx")에 속성값을 생략할 수 있음
/**
* HTTP 파라미터 이름이 변수 이름과 같으면 @RequestParam(name="xxx")의 name을 생략 가능
*/
@ResponseBody
@RequestMapping("/request-param-v3")
public String requestParamV3(@RequestParam String username,
@RequestParam int age) {
log.info("username={}, age={}", username, age);
return "ok";
}
(3) requestParamV4 - 완전생략
- 타입이 String, int, Integer등의 단순 타입이고 변수명이 같으면 @RequestParam도 생략이 가능함
- @RequestParam 애노테이션을 생략하면 required=false로 적용 됨
- 이렇게 애노테이션을 완전히 생략해도 되지만 너무 없는 것도 과할 수 있으니 같이 협업하는 개발자들과의 성향에 맞게 사용
/**
* String, int등의 단순타입이면서 매개변수명과 요청 파라미터 명이 일치할 경우 @RequestParam도 생략이 가능
*/
@ResponseBody
@RequestMapping("/request-param-v4")
public String requestParamV4(String username, int age) {
log.info("username={}, age={}", username, age);
return "ok";
}
** 참고 - 스프링 부트 3.2 부터 파라미터 이름 인식 문제가 발생함
java.lang.IllegalArgumentException: Name for argument of type [java.lang.String]
not specified, and parameter name information not found in class file either.
- @RequestParam, @PathVariable 사용시 위 예외가 발생하면 애노테이션에 이름을 생략하지 않고 항상 이름을 적어주거나 인텔리제이 세팅에서 Build and run using을 Gradle로 설정하여 사용하면 됨
(4) requestParamRequired - 필수값 설정
- 파라미터 필수 여부를 설정(required=true가 기본값)
- 필수 값 여부를 true로 설정했는데 필수 값 없이 요청이 들어오면 400예외가 발생함
** 주의!
- username이 필수 값으로 설정 되어있는데도 불구하고 /request-param-required?username= 처럼 파라미터 이름만 있고 값은 없이 요청이 오면 username의 값이 빈문자열로 오기 때문에 통과가 됨(null과 "" 는 다름)
- 기본형에 null이 입력하는 것은 불가능 하기 때문에(500예외 발생) Integer등의 래퍼 클래스를 사용하거나 defaultValue를 사용해야함
/**
* 요청파라미터에 필수 값을 설정 required = true -> 기본값
* 만약 요청이 ?username= 으로 온다면 빈문자("")로 통과됨(null이 아님)
*/
@ResponseBody
@RequestMapping("/request-param-required")
public String requestParamRequired(
@RequestParam(required = true) String username,
@RequestParam(required = false) Integer age) { // int사용 불가(int에 null을 입력할 수 없음)
log.info("username={}, age={}", username, age);
return "ok";
}
(5) requestParamDefault - 기본값 설정
- 파라미터에 값이 없는 경우 기본 값을 적용할 수 있음
- 이미 기본값이 있기 때문에 required는 의미가 없어서 생략해도 됨
- 빈 문자열("")인 경우에도 설정한 기본 값이 적용되며, 기본 값 덕분에 null이 올 수 없으므로 기본형 타입을 사용해도 됨
/**
* 요청 파라미터에 값이 없을 경우 디폴트 값을 설정 할 수 있음
* 빈 문자의 경우에도 적용 -> ?username=
*/
@ResponseBody
@RequestMapping("/request-param-default")
public String requestParamDefault(@RequestParam(defaultValue = "guest") String username,
@RequestParam(defaultValue = "-1") int age) {
log.info("username={}, age={}", username, age);
return "ok";
}
(6) requestParamMap - Map으로 조회
- 파라미터를 Map(파라미터의 값이 1개일 경우, 대부분의 경우)이나 MultiValueMap(파라미터의 값이 여러개인 경우, 거의없음)으로 조회가능
- Map은 key=value의 형태로 MultiValueMap은 배열의 형태로 조회됨
/**
* Map으로 조회 -> Map(key=value)
* MultiValueMap로도 조회 가능(파라미터의 값이 여러개일 경우) -> MultiValueMap(key=[value1,value2...])
*/
@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";
}
4. HTTP 요청 파라미터 - @ModelAttribute
1) @ModelAttribute
- 실제 개발을 해보면 요청 파라미터를 받아서 필요한 객체를 만들고 그 객체에 값을 넣어주어야하는데, 스프링은 이 과정을 완전히 자동화 해주는 기능을 제공함
(1) HelloData
- basic패키지에 요청 파라미터를 바인딩 받을 객체를 생성
- 롬복 @Data: @Getter, @Setter, @ToString, @EqualsAndHashCode, @RequiredArgsConstructor를 자동으로 적용해줌
@Data
public class HelloData {
private String username;
private int age;
}
(2) modelAttributeV1 - @ModelAttribute 사용 전
- 요청 파라미터를 받아서 필요한 객체를 만들고 그 객체에 값을 넣어주어야 함
/**
* @ModelAttribute 적용 전
*/
@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={}", username, age);
log.info("helloData={}", helloData);
return "ok";
}
(3) modelAttributeV1 - @ModelAttribute 사용 후
- 매개변수에 HelloData를 입력하면 알아서 객체가 생성이 되고 요청 파라미터의 값도 모두 들어가있음
/**
* @ModelAttribute 적용
*/
@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData) {
log.info("username={}, age={}",helloData.getUsername(), helloData.getAge());
log.info("helloData={}", helloData);
return "ok";
}
(4) 스프링 MVC의 @ModelAttribute의 실행 과정
- HelloData객체 생성(바인딩 받을 객체 생성)
- 요청 파라미터의 이름으로 HelloData 객체의 프로퍼티를 찾고 프로퍼티의 setter를 호출해서 파라미터의 값을 입력(바인딩)
- 예를 들어 파라미터 이름이 username이면 setUserName()메서드를 찾아서 호출하면서 값을 입력
** 프로퍼티란?
- 객체에 getUsername(), setUsername() 메서드가 있을 때 해당 객체는 username이라는 프로퍼티를 가지고 있다고 함
- 이때 username의 프로퍼티의 값을 변경하면 setUsername()이 호출되고, 조회하면 getUsername()이 호출됨
** 바인딩 오류
- int타입으로 설정 되어있는 age에 문자 등을 넣으면 BindException이 발생함
- 이런 바인딩 오류를 처리하는 방법은 추후 검증 부분에서 다룸
(5-1) @ModelAttribute 적용 V2 - 생략
- @ModelAttribute도 생략을 할 수 있음
- 그러나 @RequestParam도 생략할 수 있어 혼란이 있을 수 있음
(5-2) @ModelAttribute 애노테이션 생략시 스프링은 다음과 같이 동작함
- String, int, Integer등과 같은 단순 타입은 @RequestParam으로 동작
- 나머지는 @ModelAttribute로 동작(단, argument resolver로 지정해둔 타입은 제외하며 argument resolver는 추후에 다룸)
/**
* @ModelAttribute도 생략이 가능
*/
@ResponseBody
@RequestMapping("/model-attribute-v2")
public String modelAttributeV2(HelloData helloData) {
log.info("username={}, age={}",helloData.getUsername(), helloData.getAge());
log.info("username={}",helloData);
return "ok";
}
5. HTTP 요청 메시지 - 단순텍스트
1) 단순 텍스트
(1) HTTP message body에 데이터를 직접 요청
- HTTP API에서 주로 사용 : JSON(주로사용), XML, TEXT
- POST, PUT PATCH
- 요청 파라미터와 다르게 HTTP 메시지 바디를 통해 데이터가 직접 넘어오는 경우는 @RequestParam, @ModelAttribute를 사용할 수 없음(HTML Form형식은 요청 파라미터로 인정 됨)
- Postman을 활용하여 테스트
(2) RequestBodyStringController - requestBodyStringV1
- request.getInputStream()을 사용해서 직접 읽는 가장 단순한 형식
package hello.springmvc.basic.request;
@Slf4j
@Controller
public class RequestBodyStringController {
@PostMapping("/request-body-string-v1")
public void requestBodyStringV1(HttpServletRequest request, HttpServletResponse response) throws IOException {
ServletInputStream inputStream = request.getInputStream();
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
log.info("messageBody={}", messageBody);
response.getWriter().write("ok");
}
}
(3) Input, Output 스트림, Reader - requestBodyStringV2
- InputStream(Reader) : HTTP 요청 메시지 바디의 내용을 직접 조회
- OutputStream(Writer) : HTTP 응답 메시지 바디에 직접 결과 출력
// InputStream(Reader), OutStream(Writer) 직접 사용
@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");
}
(4) HttpEntity<> - requestBodyStringV3
- 스프링 MVC가 지원하는 HttpEntity : HTTP header, body 정보를 편리하게 조회 할 수 있음
- HttpEntity는 메시지 바디 정보를 직접 조회하며 요청 파라미터를 조회하는 기능(@RequestParam, @ModelAttribute)과 관계가 없으며 응답에서도 사용 가능함
(응답에서 사용시 - 메시지 바디 정보 직접 반환, 헤더 정보 포함 가능, view조회 X) - HttpEntity를 상속받은 객체들도 추가 기능을 제공함
- RequestEntity : HttpMethod, url정보 추가, 요청에서 사용
- ResponseEntity : HTTP 상태코드 설정 가능, 응답에서 사용
** 참고
- HTTP 메시지 컨버터(HttpMessageConverter): 스프링 MVC 내부에서 HTTP 메시지 바디를 읽어서 문자나 객체로 변환해서 전달해줄때 사용하는 기능
// HTTP 메시지 컨버터 활용 - HttpEntity<타입> : 스프링이 HTTP 바디에 있는것을 타입으로 바꿔서 컨버터가 동작함
@PostMapping("/request-body-string-v3")
public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) throws IOException {
String messageBody = httpEntity.getBody();
log.info("messageBody={}", messageBody);
return new HttpEntity<>("ok");
// ResponseEntity사용 예시
// return new ResponseEntity<String>("Hello World", HttpStatus.CREATED);
}
(5) @RequestBody, @ResponseBody - requestBodyStringV4(실무에서 가장많이 사용)
- @RequestBody를 사용하면 HTTP 메시지 바디 정보를 매우 편리하게 조회가 가능함
- 헤더 정보가 필요하면 @RequestHeader나 HttpEntity를 사용하면 됨
- 마찬가지로 요청 파라미터를 조회하는 @RequestParam, @ModelAttribute와 관계가 없음
- @ResponseBody로 응답 결과를 HTTP 메시지 바디에 직접 담아서 전달 할 수 있으며 이 경우에도 뷰는 사용하지 않음
// @RequestBody, @ResponseBody 애노테이션 사용 - 실무에서 제일 많이 씀
@ResponseBody
@PostMapping("/request-body-string-v4")
public String requestBodyStringV4(@RequestBody String messageBody) {
log.info("messageBody={}", messageBody);
return "ok";
}
(6) 요청 파라미터 VS HTTP 메시지 바디
- 요청 파라미터 조회 : @RequestParam, @ModelAttribute
- HTTP 메시지 바디를 직접 조회 : @RequestBody
6. HTTP 요청 메시지 - JSON
1) JSON
(1) RequestBodyJsonController - requestBodyJsonV1
- 서블릿에서 사용했던 방식처럼 HttpServletRequest의 InputStream을 사용하여 직접 HTTP 메시지 바디에서 데이터를 읽어와서 문자로 변환
- 문자로된 JSON 데이터를 Jackson 라이브러리인 objectMapper를 사용해서 자바 객체로 변환후 응답
package hello.springmvc.basic.request;
@Slf4j
@Controller
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");
}
}
(2) @RequestBody 문자 변환, @ResponseBody - requestBodyJsonV2
- @RequestBody를 활용해서 HTTP 메시지에서 데이터를 꺼낸 후 messageBody에 저장하고 objectMapper를 통해 자바 객체로 변환 후 반환
- 그러나 아직도 문자로 변환하고 다시 json으로 변환하는 과정이 불편함
/**
* @ResponseBody
* HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
*/
@ResponseBody
@PostMapping("/request-body-json-v2")
public String requestBodyJsonV2(@RequestBody String messageBody) throws IOException {
log.info("messageBody={}", messageBody);
HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
return "ok";
}
(3) @RequestBody 객체 변환 - requestBodyJsonV3(가장 편리)
- @RequestBody HelloData data : @RequestBody에 직접 만든 객체를 지정할 수 있음
- HttpEntity나 @RequestBody를 사용하면 HTTP 메시지 컨버터가 HTTP 메시지 바디의 내용을 우리가 원하는 문자나 객체로 변환해주는데, JSON도 객체로 변환해줌
- 즉, V2에서 objectMapper.readValue() 코드의 작업을 대신 처리해줌
- @RequestBody는 생략이 불가능: 생략하면 @ModelAttribute가 적용되어 요청파라미터를 찾는 코드가 되어버려서 기본값이 반환됨
/**
* @RequestBody 생략 불가능(생략하면 @ModelAttribute가 적용됨)
* HttpMessageConverter 사용 -> MappingJackson2HttpMessageConverter (content-type:application/json)
*/
@ResponseBody
@PostMapping("/request-body-json-v3")
public String requestBodyJsonV3(@RequestBody HelloData data) throws IOException {
log.info("messageBody={}", data);
log.info("username={}, age={}", data.getUsername(), data.getAge());
return "ok";
}
(4) HttpEntity 사용 - requestBodyJsonV4
- HttpEntity를 사용해도 되지만 메시지 바디의 내용을 직접 꺼내야하는 코드가 필요함
@ResponseBody
@PostMapping("/request-body-json-v4")
public String requestBodyJsonV4(HttpEntity<HelloData> httpEntity) {
HelloData data = httpEntity.getBody();
log.info("messageBody={}", data);
log.info("username={}, age={}", data.getUsername(), data.getAge());
return "ok";
}
(5) Json으로 반환 - requestBodyJsonV5
- @ResponseBody로 해당 객체를 HTTP 메시지 바디에 직접 넣어줄 수 있음
- 해당 경우에도 HttpEntity사용 가능(V4 참고)
- JSON 요청 -> HTTP 메시지 컨버터 -> 객체 -> HTTP 메시지 컨버터 -> JSON 응답
@ResponseBody
@PostMapping("/request-body-json-v5")
public HelloData requestBodyJsonV5(@RequestBody HelloData data) {
log.info("messageBody={}", data);
log.info("username={}, age={}", data.getUsername(), data.getAge());
return data;
}