-
Spring MVC (6) - 스프링 MVC 기본 기능Web/Spring 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으로 바꿔서 응답해준다.
'Web > Spring' 카테고리의 다른 글
스프링 MVC - 웹 페이지 만들기 (0) 2023.10.15 Spring MVC (6) - 스프링 MVC 기본기능 (2) : Http Body (0) 2023.10.14 SpringMVC (5) - 스프링 MVC 구조 이해 (0) 2023.10.02 SpringMVC (4.5) - FrontController V5 [어댑터 패턴 - 중요] (0) 2023.10.02 SpringMVC (4) - MVC 프레임워크 만들기 [중요] (0) 2023.10.02