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
댓글을 사용할 수 없습니다.