[spring] SpringMVC 의 View

GuruneLee·2023년 1월 1일
0

Let's Study 공부해요~

목록 보기
25/36
post-thumbnail

Spring MVC

MVC 패턴을 지원하기 위해 만들어진 Spring 웹 모듈.
( MVC 패턴이란, Model View Controller 의 요소를 이용하여, 비지니스 로직과 화면을 분리한 소프트웨어 디자인 패턴인다 )

Spring MVC 는 사용자의 다양한 Http Request 처리를 지원하며, MVC 패턴 구현을 위해 'DispatcherServlet' 'ModelAndView' 'ViewResolver' 'View' 'Handler' 등의 컴포넌트를 지원한다. 

Spring MVC 에 관한 자세한 내용을 다음 공식 문서를 확인해보자.

현 문서는 MVC 구성 요소 중 'View' 에 대해 정리한다.

View 

View 의 역할

view 는 단어 뜻 그대로 '보이는 것' 을 담당한다. 즉 데이터가 보여지는 방식에 책임이 있다.

view 는 최종적으론 HttpServletResponse 에 데이터를 적재하고, 헤더를 기록하는 등의 작업을 하게된다. 이는 이후 코드로 확인하자.

Data, View에 닿기를...!

View 가 동작하는 것을 확인하기 전에 HandlerAdapterModelAndView, 그리고 ViewResolver 가 대략적으로 동작하는 방식을 살펴보자.

이는, data 가 view 에 도달하는 방식과 밀접한 관련이 있다.
그림을 보자. SpringMVC 의 간략화된 구조이다.

(내가 그린건 아니다. velog 의 블로거에게 축복을.)

Handler (Controller) 호출, ModelAndView 의 반환

data 는 1의 요청을 타고와서 3으로 흘러들어갈 수도 있고, 요청에 의해 Controller에서 생성될 수도 있다.

어떻게 data가 생성되던 Controller 는 data와 view 를 감싼 ModelAndView 라는 객체를 DispatcherServlet에 반환하게 된다 (정확히 말하면 HandlerAdapter 에서 반환됨).

  • 여기서 짚고 넘어갈 것은 ModelAndView 에서 view 라는 이름의 멤버는 Object 타입으로 되어있다는 것이다. 즉, 진짜 View를 때려넣을 수도 있고, View_name 만 넘겨서 view resolver에게 view를 찾는 책임을 전가 할 수도 있다.
// org.springframework.web.servlet.ModelAndView
public class ModelAndView {
	/** View instance or view name String. */
	@Nullable
 	🎉 private Object view;
	...
} 

++ 살짝 먼저 보여주자면, ModelAndView 에 View 가 있는지, ViewName이 있는지에 따라 ViewResolver를 타는지 여부가 나뉜다

// org.springframework.web.servlet.DispatcherServlet
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
   ...
   View view;
   String viewName = mv.getViewName();
   if (viewName != null) {
      🎉 view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
      ...
   }
   else {
      🎉 view = mv.getView();
      ...
   }
   ...
}

ViewResolver 호출, View의 반환

ModelAndView를 반환받은 DispatcherServlet은 몇 가지 과정 후에 render 메서드를 동작한다 (바로 위에서 보여준 함수).

( 정확히는 doDispatchprocessDispatchResultrender 스택으로 호출된다 )

render 메서드에선 필요할 때 resolveViewName 메서드를 호출하는데, 이 메서드에서 ViewResolver를 호출하고, View를 반환한다.

// org.springframework.web.wervlet.DispatcherServletprotected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
	Locale locale, HttpServletRequest request) throws Exception {
	if (this.viewResolvers != null) {
  		🎉 for (ViewResolver viewResolver : this.viewResolvers) {
			View view = viewResolver.resolveViewName(viewName, locale);
			if (view != null) {
				return view;
			}
		}
	}
	return null;
}

DispatcherServlet init 시, 존재하는 모든 view resolver 를 불러와 저장한다. 

resolveViewName의 for 문을 보면, 모든 view resolver의 resolveViewName 메서드를 호출해서 View 를 반환하는지 안하는지 체크한다.
( 스프링에서 이런 코드 볼 때 마다 재밌다. 수면 아래서 열심히 헤엄치는 백조가 생각난다. )

그래서 아무튼 view resolver에서 제대로된 View 가 반환되면 이를 render 함수에서 사용할 수 있도록 반환한다.

View 여, render 하라.

자, 이제 Viewrender 함수를 호출할 차례다.
Dispatcher 서블릿의 render 함수에서 View를 받아왔으면, 이제 정말 render 해야한다. 

이 단계는 DispatcherServlet이 아닌 View 객체에 책임을 위임하여 처리하게 된다 (Delegation).

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
   ...
   View view;
   ...
   try {
      if (mv.getStatus() != null) {
         response.setStatus(mv.getStatus().value());
      }
      🎉 view.render(mv.getModelInternal(), request, response);
   }
   catch (Exception ex) {
      ...
      throw ex;
   }
}

View는 인터페이스다. 따라서 구현체마다 render 함수의 구현이 다르다.
( 나중에 직접 View를 정의해야할 일이 있으므로, 위 사실은 중요한 열쇠가 된다. )

Spring 엔 여러 ViewResolver가 등록되어있고, 각 resolver 마다 반환하는 View 구현체가 다르니 잘 찾아보길 바란다.

난 예시로 HtmlResourceView 코드를 살펴보았다. 생각보다 간단한 코드이다. (쓰면서 알았는데, 이거 뷰는 에러 화면 띄울 때 사용하나보다...)

//org.springframework.boot.autoconfigure.web.servlet.error.DefaultErrorViewResolver

public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
	...
	private static class HtmlResourceView implements View {
		private Resource resource;

		HtmlResourceView(Resource resource) {
			this.resource = resource;
		}

		@Override
		public String getContentType() {
			return MediaType.TEXT_HTML_VALUE;
		}

		@Override
		public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
				throws Exception {
   			🎉 response.setContentType(getContentType());
   			🎉 FileCopyUtils.copy(this.resource.getInputStream(), response.getOutputStream());
		}

	}
}

DefaultErrorViewResolver가 반환하는 View 로서, 서브 클래스로 선언되어있다. render 메서드의 길이가 매우 짧다.

🎉 reposne.setContentType(getContentType())

  • HttpServletResponse 의 ContentType 을 세팅한다
    🎉 FileCopyUtils.copy(this.resource.getInputStream(), response.getOutputStream())
  • resource 라는 데이터를 HttpServletResponse의 ServletOutputStream에 담는 부분이다

View가 결국엔 응답에 데이터를 싣는 역할을 한다는것이 드러난 순간이다...!
( 시간나면 DefulatErrorViewResolver 도 꼭 살펴보자 )

  • 여기서도 짚고 넘어가면 좋을게 있다. 바로 ServletResponseData를 싣는 방법이다.

간단하게 말하면, 
1. Binary Data - getOutputStream으로 가져온 ServletOuputStream을 받아 담는다 (MIME type..?)
2. Character Data - getWriter로 가져온 printWriter에 담는다

잘 이해가 안가는 부분이지만, 이렇게 구분된다는 사실만 일단 기억하자. Http 응답 구조에 대해 더 공부하고 정리하면 다시 돌아보도록 하자.

이렇게 View 의 동작에 대해 알아보았다.

profile
Today, I Shoveled AGAIN....

0개의 댓글