Springboot MVC 파헤치기(9) ViewResolver, HttpMessageConverters
ViewResolver란?
사용자가 요청한 정보를 랜더링(html을 만드는 과정)하는 역할을 합니다. BeanNameViewResolver의 경우 DispatcherServlet 내에서 랜더링이 render()로 진행되고, InternalResourceViewResolver는 InternalResourceView의 forward()를 통해서 진행됩니다. Springboot는 container를 초기화할 때 InternalResoureceViewResolver와 BeanNameViewResolver를 bean으로 자동 등록합니다.
BeanNameViewResolver은 bean이름으로 찾아서 반환하고, InternalResourceViewResolver는 JSP를 사용할 때 사용됩니다. 별도로 Resolver를 custom해서 사용할 수도 있습니다.
BeanNameViewResolver
View이름과 동일한 이름을 가지는 Bean을 View로 생성합니다.
public class BeanNameViewResolver extends WebApplicationObjectSupport implements ViewResolver, Ordered {
private int order = Ordered.LOWEST_PRECEDENCE; // default: same as non-Ordered
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws BeansException {
ApplicationContext context = obtainApplicationContext();
if (!context.containsBean(viewName)) {
return null;
}
if (!context.isTypeMatch(viewName, View.class)) {
if (logger.isDebugEnabled()) {
logger.debug("Found bean named '" + viewName + "' but it does not implement View");
}
return null;
}
return context.getBean(viewName, View.class);
}
}
- resolveViewName()을 통해서 View를 찾고 반환합니다.
그 후 DispatchServlet에서는 찾은 View를 랜더링합니다.
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
Locale locale =
(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
response.setLocale(locale);
View view;
String viewName = mv.getViewName();
if (viewName != null) {
// We need to resolve the view name.
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
}
else {
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
}
}
if (logger.isTraceEnabled()) {
logger.trace("Rendering view [" + view + "] ");
}
try {
if (mv.getStatus() != null) {
request.setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, mv.getStatus());
response.setStatus(mv.getStatus().value());
}
view.render(mv.getModelInternal(), request, response); // 랜더링
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "]", ex);
}
throw ex;
}
}
InternalResourceViewResolver
JSP 파일이나 HTML 파일과 같이 웹애플리케이션의 내부 자원을 이용해서 AbstractUrlBasedView 타입의 객체를 반환합니다. prefix, suffix를 이용하여 url을 간편화 할 수 있습니다.
public class InternalResourceViewResolver extends UrlBasedViewResolver {
private static final boolean jstlPresent = ClassUtils.isPresent(
"javax.servlet.jsp.jstl.core.Config", InternalResourceViewResolver.class.getClassLoader());
@Nullable
private Boolean alwaysInclude;
public InternalResourceViewResolver() {
Class<?> viewClass = requiredViewClass();
if (InternalResourceView.class == viewClass && jstlPresent) {
viewClass = JstlView.class;
}
setViewClass(viewClass);
}
// prefix:앞에 붙는 String
// suffix:뒤에 붙는 String
public InternalResourceViewResolver(String prefix, String suffix) {
this();
setPrefix(prefix);
setSuffix(suffix);
}
public void setAlwaysInclude(boolean alwaysInclude) {
this.alwaysInclude = alwaysInclude;
}
@Override
protected Class<?> requiredViewClass() {
return InternalResourceView.class;
}
@Override
protected AbstractUrlBasedView instantiateView() {
return (getViewClass() == InternalResourceView.class ? new InternalResourceView() :
(getViewClass() == JstlView.class ? new JstlView() : super.instantiateView()));
}
@Override
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
InternalResourceView view = (InternalResourceView) super.buildView(viewName);
if (this.alwaysInclude != null) {
view.setAlwaysInclude(this.alwaysInclude);
}
view.setPreventDispatchLoop(true);
return view;
}
}
- buildView() 통해서 view를 반환합니다.
- prefix와 suffix로 조건에 맞는 view를 찾을 수 있게 해줍니다.
InternalViewResource
@Override
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
exposeModelAsRequestAttributes(model, request);
exposeHelpers(request);
String dispatcherPath = prepareForRendering(request, response);
RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
if (rd == null) {
throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
"]: Check that the corresponding file exists within your web application archive!");
}
if (useInclude(request, response)) {
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including [" + getUrl() + "]");
}
rd.include(request, response);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to [" + getUrl() + "]");
}
rd.forward(request, response); // 반환
}
}
- 랜더링하여 반환을 합니다.
WebMvcConfiguration ViewResolver 등록 메서드
public class WebMvcAutoConfiguration {
// 중략
@Bean
@ConditionalOnMissingBean
public InternalResourceViewResolver defaultViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix(this.mvcProperties.getView().getPrefix());
resolver.setSuffix(this.mvcProperties.getView().getSuffix());
return resolver;
}
// 중략
}
- application.properties 혹은 application.yml에서 prefix와 suffix를 지정하여 변경할 수 있습니다.
HttpMessageConverters
ViewResolver가 View에 데이터를 랜더링해서 html 혹은 jsp를 반환하는 것이 었다면, HttpMessageConverters는 HTTP 요청 혹은 응답 본문을 변경할 때 사용됩니다. 이때 byte[] 타입은 ByteArrayHttpMessageConveter String 형식이라면 StringHttpMessageConverter 객체라면 MappingJackson2HttpMessageConverter가 작동합니다. 그렇다면 ViewResolver를 적용할지 HttpMessageConveter를 적용할지 결정하는 것은 @RequestBody, @ResponseBody의 여부입니다.
HttpMessageConverter 적용시점은 @RequestMapping이 처리되는 RequestMappingHandlerAdapter에서 적용됩니다.
@RequestBody, @ResponseBody x -> ViewResolver
@RequestBody, @ResponseBody o -> HttpMessageConveter
HttpMessageConveterInterface
public interface HttpMessageConverter<T> {
boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
List<MediaType> getSupportedMediaTypes();
default List<MediaType> getSupportedMediaTypes(Class<?> clazz) {
return (canRead(clazz, null) || canWrite(clazz, null) ?
getSupportedMediaTypes() : Collections.emptyList());
}
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
- canRead()와 canWrite()는 MediaType이 적합하여 변환할 수 있는지 체크합니다.
- canRead(), canWrite()를 통과한다면, read()와 write()를 수행합니다.
canRead()가 작동하는지 디버깅을 해보겠습니다.
url: localhost:8080/requestBody Method: GET
@RestController
public class ParentController{
@GetMapping("/requestParam")
public int requestParam(@RequestBody int size) { // RequestBody가 있으므로 HttpMessageConverter 동작
return size;
}
}
HttpMessageConverter를 구현하고 있는 AbstractHttpMessageConverter canRead 메서드 입니다.
- Application/JSON 형식이기에 HttpMessageConverter를 구현하고 있는 AbstractMessageConverter가 동작하는 것을 알 수 있습니다.
정리하기
ViewResolver
- BeanNameViewResolver혹은 InternalResourceViewResolver를 통해서 랜더링을 진행한다.
- html 혹은 jsp 같은 view를 반환할 때 사용된다.
- ModelAndView가 반환되는 Dispatcher 시점에서 View를 랜더링한다.
HttpMessageConverter
- HTTP 요청 혹은 응답 본문에 사용됩니다.
- @RequestBody, @ResponseBody가 붙은 것들에 적용됩니다.
- byte[]의 경우 ByteArrayHttpMessageConveter
- String의 경우 StringHttpMessageConveter
- 객체의 경우 MappingJackson2HttpMessageConveter
- 적용 시점은 RequestMappingHandlerAdapter에서 적용됩니다.
지금까지 ViewResolver와 HttpMessageConveter에 대해서 알아봤습니다. 감사합니다.
'SpringBoot > spring mvc' 카테고리의 다른 글
Springboot MVC 파헤치기(11) Validation 동작과정 (0) | 2022.04.23 |
---|---|
Springboot MVC 파헤치기(10) Validation 유효성 검증 (0) | 2022.04.20 |
Springboot MVC 파헤치기(8) @ModelAttribute, @RequestParam, @PathVariable @ResponseBody @RequestBody 동작 과정 (0) | 2022.03.15 |
Springboot MVC 파헤치기(5) @RequestParam (0) | 2022.03.15 |
Springboot MVC 파헤치기(7) @RequestBody, @ResponseBody (0) | 2022.03.15 |
댓글
이 글 공유하기
다른 글
-
Springboot MVC 파헤치기(11) Validation 동작과정
Springboot MVC 파헤치기(11) Validation 동작과정
2022.04.23 -
Springboot MVC 파헤치기(10) Validation 유효성 검증
Springboot MVC 파헤치기(10) Validation 유효성 검증
2022.04.20 -
Springboot MVC 파헤치기(8) @ModelAttribute, @RequestParam, @PathVariable @ResponseBody @RequestBody 동작 과정
Springboot MVC 파헤치기(8) @ModelAttribute, @RequestParam, @PathVariable @ResponseBody @RequestBody 동작 과정
2022.03.15 -
Springboot MVC 파헤치기(5) @RequestParam
Springboot MVC 파헤치기(5) @RequestParam
2022.03.15