반응형
# 스프링 MVC 기본 기능
## 응답 - 정적 리소스, 뷰 템플릿
- 스프링(서버)에서 응답 데이터를 만드는 방법은 크게 3가지이다.
1. 정적 리소스
- 예) 웹 브라우저에 정적인 HTML, css, js을 제공할 때, 정적 리소스 사용.
2. 뷰 템플릿 사용
- 예) 웹 브라우저에 동적인 HTML을 제공할 때, 뷰 템플릿 사용.
3. HTTP 메시지 사용
- HTTP API를 제공하는 경우, HTML이 아니라 데이터를 전달해야 하므로, HTTP 메시지 바디에 JSON 같은 형식으로 데이터를 실어 보낸다.
정적 리소스
- 스프링 부트는 클래스패스의 다음 디렉토리에 있는 정적 리소스를 제공 : /static, /public, /resources, /META-INF/resources
- 정적 리소스 : 해당 파일을 변경 없이 그대로 서비스하는 것.
뷰 템플릿
- 뷰 템플릿을 거쳐 HTML이 생성되고, 뷰가 응답을 만들어서 전달.
- 일반적으로 HTML을 동적으로 생성하는 용도로 사용 하지만, 다른 것들도 가능. (뷰 템플릿이 만들 수 있는 것이라면 뭐든지 가능.)
- 뷰 템플릿 경로
src/main/resources/templates
- hello.html (뷰 템플릿)
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p th:text="${data}">empty</p>
</body>
</html>
- ResponseViewController. (뷰 템플릿 사용을 위한 컨트롤러)
package hello.springmvc.basic.response;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class ResponseViewController {
@RequestMapping("/response-view-v1")
public ModelAndView responseViewV1() {
ModelAndView mav = new ModelAndView("response/hello")
.addObject("data", "hello!");
return mav;
}
}
- String을 반환하는 경우 - View or HTTP 메시지
@RequestMapping("/response-view-v2")
public String responseViewV2(Model model) {
model.addAttribute("data", "hello!");
return "response/hello";
}
- @ResponseBody 가 없으면 response/hello 로 뷰 리졸버가 실행되어 뷰를 찾고, 렌더링.
- @ResponseBody 가 있으면 뷰 리졸버를 실행하지 않고, HTTP 메시지 바디에 직접 response/hello 라는 문자 입력.
- Void를 반환하는 경우 - 해당 방식은 명시성이 떨어지고 이렇게 딱 맞는 경우도 없어서, 권장하지 않는다.
@RequestMapping("/response/hello")
public void responseViewV3(Model model) {
model.addAttribute("data", "hello!");
}
Thymeleaf 스프링 부트 설정
- 다음 라이브러리를 추가 (build.gradle에 추가)
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
- 스프링 부트가 자동으로 ThymeleafViewResolver 와 필요한 스프링 빈들을 등록. 그리고 아래 설정도 사용. (기본 값)
- application.properties
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
## HTTP 응답 - HTTP API, 메시지 바디에 직접 입력
- HTTP API를 제공하는 경우, HTML이 아닌 데이터를 전달해야 하므로, HTTP 메시지 바디에 JSON 같은 형식으로 데이터를 실어 보낸다.
ResponseBodyController.
package hello.springmvc.basic.response;
import hello.springmvc.basic.HelloData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@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("userA");
helloData.setAge(25);
return new ResponseEntity<>(helloData, HttpStatus.OK);
}
@ResponseStatus(HttpStatus.OK)
@GetMapping("/response-body-json-v2")
public HelloData responseBodyJsonV2() {
HelloData helloData = new HelloData();
helloData.setUsername("userA");
helloData.setAge(25);
return helloData;
}
}
responseBodyJsonV1
- ResponseEntity 를 반환. HTTP 메시지 컨버터를 통해 JSON 형식으로 변환되어서 반환.
responseBodyJsonV2
- ResponseEntity 는 HTTP 응답 코드를 설정할 수 있는데, @ResponseBody 를 사용하면 설정하기 까다롭다.
- @ResponseStatus(HttpStatus.OK) 애노테이션 : 응답 코드 설정할 수 있는 애노테이션. 물론 애노테이션이기 때문에 응답 코드를 동적으로 변경할 수는 없다. (프로그램 조건에 따라 동적으로 변경하려면 ResponseEntity 사용.)
@RestController (@Controller + @ResponseBody)
- @Controller 대신 @RestController 애노테이션을 사용하면, 해당 컨트롤러에 모두 @ResponseBody 가 적용되는 효과.
- 따라서 뷰 템플릿을 사용하는 것이 아닌, HTTP 메시지 바디에 직접 데이터를 입력한다.
- 이름 그대로 Rest API(HTTP API)를 만들 때 사용하는 컨트롤러.
- @ResponseBody 는 클래스 레벨에 두면 전체에 메서드에 적용, @RestController 에노테이션 안에 @ResponseBody 가 적용되어 있다.
## HTTP 메시지 컨버터
- 뷰 템플릿으로 HTML을 생성해서 응답하는 것이 아닌, HTTP API처럼 JSON 데이터를 HTTP 메시지 바디에서 직접 읽거나 쓰는 경우 HTTP 메시지 컨버터를 사용하면 편리하다.
스프링 MVC는 다음의 경우 HTTP 메시지 컨버터를 적용.
- HTTP 요청 : @RequestBody , HttpEntity(RequestEntity)
- HTTP 응답 : @ResponseBody , HttpEntity(ResponseEntity)
HTTP 메시지 컨버터 인터페이스
org.springframework.http.converter.HttpMessageConverter
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.http.converter;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
/**
* Strategy interface for converting from and to HTTP requests and responses.
*
* @author Arjen Poutsma
* @author Juergen Hoeller
* @author Rossen Stoyanchev
* @since 3.0
* @param <T> the converted object type
*/
public interface HttpMessageConverter<T> {
/**
* Indicates whether the given class can be read by this converter.
* @param clazz the class to test for readability
* @param mediaType the media type to read (can be {@code null} if not specified);
* typically the value of a {@code Content-Type} header.
* @return {@code true} if readable; {@code false} otherwise
*/
boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
/**
* Indicates whether the given class can be written by this converter.
* @param clazz the class to test for writability
* @param mediaType the media type to write (can be {@code null} if not specified);
* typically the value of an {@code Accept} header.
* @return {@code true} if writable; {@code false} otherwise
*/
boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
/**
* Return the list of media types supported by this converter. The list may
* not apply to every possible target element type and calls to this method
* should typically be guarded via {@link #canWrite(Class, MediaType)
* canWrite(clazz, null}. The list may also exclude MIME types supported
* only for a specific class. Alternatively, use
* {@link #getSupportedMediaTypes(Class)} for a more precise list.
* @return the list of supported media types
*/
List<MediaType> getSupportedMediaTypes();
/**
* Return the list of media types supported by this converter for the given
* class. The list may differ from {@link #getSupportedMediaTypes()} if the
* converter does not support the given Class or if it supports it only for
* a subset of media types.
* @param clazz the type of class to check
* @return the list of media types supported for the given class
* @since 5.3.4
*/
default List<MediaType> getSupportedMediaTypes(Class<?> clazz) {
return (canRead(clazz, null) || canWrite(clazz, null) ?
getSupportedMediaTypes() : Collections.emptyList());
}
/**
* Read an object of the given type from the given input message, and returns it.
* @param clazz the type of object to return. This type must have previously been passed to the
* {@link #canRead canRead} method of this interface, which must have returned {@code true}.
* @param inputMessage the HTTP input message to read from
* @return the converted object
* @throws IOException in case of I/O errors
* @throws HttpMessageNotReadableException in case of conversion errors
*/
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
/**
* Write an given object to the given output message.
* @param t the object to write to the output message. The type of this object must have previously been
* passed to the {@link #canWrite canWrite} method of this interface, which must have returned {@code true}.
* @param contentType the content type to use when writing. May be {@code null} to indicate that the
* default content type of the converter must be used. If not {@code null}, this media type must have
* previously been passed to the {@link #canWrite canWrite} method of this interface, which must have
* returned {@code true}.
* @param outputMessage the message to write to
* @throws IOException in case of I/O errors
* @throws HttpMessageNotWritableException in case of conversion errors
*/
void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
- HTTP 메시지 컨버터는 HTTP 요청, HTTP 응답 둘 다 사용.
canRead() , canWrite() : 메시지 컨버터가 해당 클래스, 미디어타입을 지원하는지 체크.
read() , write() : 메시지 컨버터를 통해서 메시지를 읽고 쓰는 기능.
스프링 부트 기본 메시지 컨버터 (일부 생략)
0 = ByteArrayHttpMessageConverter
1 = StringHttpMessageConverter
2 = MappingJackson2HttpMessageConverter
주요한 메시지 컨버터
- ByteArrayHttpMessageConverter : byte[] 데이터 처리.
클래스 타입: byte[] , 미디어타입: */*
요청 예) @RequestBody byte[] data
응답 예) @ResponseBody return byte[] 쓰기 미디어타입 application/octet-stream
- StringHttpMessageConverter : String 문자로 데이터를 처리한다
클래스 타입: String , 미디어타입: */*
요청 예) @RequestBody String data
응답 예) @ResponseBody return "ok" 쓰기 미디어타입 text/plain
- MappingJackson2HttpMessageConverter : application/json
클래스 타입: 객체 또는 HashMap , 미디어타입 application/json 관련
요청 예) @RequestBody HelloData data
응답 예) @ResponseBody return helloData 쓰기 미디어타입 application/json 관련
HTTP 요청 데이터 읽기
HTTP 요청이 오고, 컨트롤러에서 @RequestBody , HttpEntity 파라미터를 사용한다.
메시지 컨버터가 메시지를 읽을 수 있는지 확인하기 위해 canRead() 호출.
대상 클래스 타입을 지원하는가 : 예) @RequestBody 의 대상 클래스 ( byte[] , String , HelloData )
HTTP 요청의 Content-Type 미디어 타입을 지원하는가 : 예) text/plain , application/json , */*
canRead() 조건을 만족하면 read() 를 호출해서 객체 생성하고, 반환.
HTTP 응답 데이터 생성
컨트롤러에서 @ResponseBody , HttpEntity 로 값 반환.
메시지 컨버터가 메시지를 쓸 수 있는지 확인하기 위해 canWrite() 를 호출.
대상 클래스 타입을 지원하는가 : 예) return의 대상 클래스 ( byte[] , String , HelloData )
HTTP 요청의 Accept 미디어 타입을 지원하는가(정확히는 @RequestMapping 의 produces) : 예) text/plain , application/json , */*
canWrite() 조건을 만족하면 write() 를 호출해서 HTTP 응답 메시지 바디에 데이터를 생성.
## 요청 매핑 헨들러 어뎁터 구조 (RequestMappingHandlerAdapter)
- @RequestMapping 을 처리하는 핸들러 어댑터인 RequestMappingHandlerAdapter (요청 매핑 헨들러 어뎁터)
ArgumentResolver (파라미터 처리!)
- 애노테이션 기반의 컨트롤러는 매우 다양한 파라미터를 사용. HttpServletRequest , Model 은 물론이고, @RequestParam , @ModelAttribute 같은 애노테이션 그리고 @RequestBody , HttpEntity 같은 HTTP 메시지를 처리하는 부분까지 큰 유연함을 보여주었다.
- 이렇게 파라미터를 유연하게 처리할 수 있는 이유가 바로 ArgumentResolver 덕분.
- 애노테이션 기반 컨트롤러를 처리하는 RequestMappingHandlerAdaptor 는 바로 이 ArgumentResolver 를 호출해 컨트롤러(핸들러)가 필요로 하는 다양한 파라미터의 값(객체)을 생성. 그리고 파리미터의 값이 모두 준비되면 컨트롤러를 호출하면서 값을 넘겨준다.
- 스프링은 30개가 넘는 ArgumentResolver 를 기본으로 제공. (HandlerMethodArgumentResolver 인데 줄여서 ArgumentResolver 라고 부른다)
/*
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.method.support;
import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
/**
* Strategy interface for resolving method parameters into argument values in
* the context of a given request.
*
* @author Arjen Poutsma
* @since 3.1
* @see HandlerMethodReturnValueHandler
*/
public interface HandlerMethodArgumentResolver {
/**
* Whether the given {@linkplain MethodParameter method parameter} is
* supported by this resolver.
* @param parameter the method parameter to check
* @return {@code true} if this resolver supports the supplied parameter;
* {@code false} otherwise
*/
boolean supportsParameter(MethodParameter parameter);
/**
* Resolves a method parameter into an argument value from a given request.
* A {@link ModelAndViewContainer} provides access to the model for the
* request. A {@link WebDataBinderFactory} provides a way to create
* a {@link WebDataBinder} instance when needed for data binding and
* type conversion purposes.
* @param parameter the method parameter to resolve. This parameter must
* have previously been passed to {@link #supportsParameter} which must
* have returned {@code true}.
* @param mavContainer the ModelAndViewContainer for the current request
* @param webRequest the current request
* @param binderFactory a factory for creating {@link WebDataBinder} instances
* @return the resolved argument value, or {@code null} if not resolvable
* @throws Exception in case of errors with the preparation of argument values
*/
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
- 동작방식 : ArgumentResolver 의 supportsParameter() 를 호출 해당 파라미터를 지원하는지 체크, 지원하면 resolveArgument() 를 호출해서 실제 객체를 생성. 그리고 이렇게 생성된 객체가 컨트롤러 호출 시 넘어간다.
ReturnValueHandler
- HandlerMethodReturnValueHandler 를 줄여 ReturnValueHandle 라고 부른다.
- ArgumentResolver 와 비슷한데, 이것은 응답 값을 변환하고 처리.
- 컨트롤러에서 String으로 뷰 이름을 반환해도, 동작하는 이유가 바로 ReturnValueHandler 덕분이다.
- 스프링은 10여개가 넘는 ReturnValueHandler 를 지원. (ModelAndView , @ResponseBody , HttpEntity , String 등)
HTTP 메시지 컨버터
- HTTP 메시지 컨버터를 사용하는 @RequestBody 도 컨트롤러가 필요로 하는 파라미터의 값에 사용.
- @ResponseBody 도 컨트롤러의 반환 값을 이용.
- ArgumentResolver은 HTTP 메서지 컨버터 사용.
요청
- @RequestBody 를 처리하는 ArgumentResolver, HttpEntity 를 처리하는 ArgumentResolver 가 존재.
- 이 ArgumentResolver 들이 HTTP 메시지 컨버터를 사용해서 필요한 객체를 생성하는 것.
응답
- @ResponseBody 와 HttpEntity 를 처리하는 ReturnValueHandler 가 존재. 그리고 여기에서 HTTP 메시지 컨버터를 호출해서 응답 결과를 만든다.
- 스프링 MVC는 @RequestBody @ResponseBody 가 있으면 RequestResponseBodyMethodProcessor (ArgumentResolver), HttpEntity 가 있으면 HttpEntityMethodProcessor (ArgumentResolver)를 사용.
확장
- 스프링은 다음을 모두 인터페이스로 제공. (필요하면 언제든 기능 확장가능.)
- HandlerMethodArgumentResolver
- HandlerMethodReturnValueHandler
- HttpMessageConverter
- 기능 확장 : WebMvcConfigurer 를 상속 받아 스프링 빈으로 등록하면 된다.
반응형
'인프런 강의 학습 > 스프링 MVC 1' 카테고리의 다른 글
스프링 MVC 11일차_스프링 MVC 웹 페이지 만들기 (0) | 2022.03.14 |
---|---|
스프링 MVC 9일차_스프링 MVC_기본 기능_2 (0) | 2022.02.28 |
스프링 MVC 8일차_스프링 MVC_기본 기능_1 (0) | 2022.02.27 |
스프링 MVC 7일차_스프링 MVC 구조이해. (0) | 2022.02.26 |
스프링 MVC 6일차_MVC 프레임워크 만들기_2 (0) | 2022.02.25 |