티스토리 뷰

@Controller의 반환값이 문자열로 뷰 이름을 반환하는 것이 아닌 다른 타입의 반환값을 가지게 된다면 어떻게 될까?

클라이언트 요청 및 응답 과정

클라이언트의 요청은 크게 아래와 같은 흐름으로 진행된다.

  1. 요청이 DispatcherServlet으로 들어온다.
  2. 요청 정보를 바탕으로 HandlerMapping을 통해 핸들러를 찾는다.
  3. 핸들러 정보를 바탕으로 HandlerAdapter를 통해 핸들러 어댑터를 찾는다.
  4. 핸들러 어댑터가 핸들러(컨트롤러)로 요청을 위임한다.
  5. 핸들러 메서드 실행 후 Model과 View를 반환한다.
  6. 뷰 이름을 ViewResolver에 전달하고 뷰 리졸버는 해당하는 뷰를 반환한다.
  7. 디스패처 서블릿은 뷰에게 모델을 전달하고 모델 데이터를 뷰에 렌더링 한다.
  8. 최종적인 뷰를 클라이언트 응답으로 반환한다.

@Controller

실행 흐름

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {

    //...

    try {
    	doDispatch(request, response);
    }

    //...
}

디스패처 서블릿의 doService에 들어온 요청은 doDispatch로 전달된다.

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {

    //...

    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

    //...
}

doDispatch에서는 요청 결과 처리를 위해 processDispatchResult를 호출한다.

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
		@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
		@Nullable Exception exception) throws Exception {

    //...

    // Did the handler return a view to render?
    if (mv != null && !mv.wasCleared()) {
    	render(mv, request, response);
    	if (errorView) {
    		WebUtils.clearErrorRequestAttributes(request);
    	}
    }

    //...
}

processDispatchResultrender를 통해 뷰로 반환할 수 있는지 확인하게 된다.

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {

    //...

    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() + "'");
    	}
    }

    //...
}

컨트롤러에서 처리된 ModelAndView에서 뷰 이름을 얻고 resolveViewName을 호출한다.

protected 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;
}

등록된 뷰 리졸버 중 뷰 이름을 통해 처리할 수 있는 뷰 리졸버를 찾게 된다. 만약 처리할 수 있는 뷰 리졸버가 존재하지 않는 경우 null을 반환하게 된다. 최종 반환값이 null인 경우 해당 뷰를 처리할 수 없기에 예외가 발생하게 된다.

ViewResolver

@Controller 어노테이션을 사용하면 반환값을 처리하는 과정에서 ViewResolver가 사용된다.

 

뷰 리졸버란 스프링 프레임워크에서 뷰 객체를 생성하는 방법을 결정하는 인터페이스이다. 뷰 리졸버는 컨트롤러가 반환하는 뷰 이름을 뷰 객체로 변환하는데, 변환된 뷰 객체는 디스패처 서블릿이 클라이언트에게 반환하는 응답을 생성하기 위해 사용된다.

 

여러 개의 뷰 리졸버를 등록하여 사용할 수 있다. 여러 개의 뷰 리졸버는 등록된 순서대로 검색된다. 이를 통해 뷰 이름을 해석하는 방법을 동적으로 변경할 수 있다.

처음에는 위와 같이 뷰 리졸버가 등록되어 있다.

 

[ ContentNegotiatingViewResolver ]

 

뷰를 찾기 위해 요청 URL의 확장자와 AcceptHeader를 사용하는 뷰 리졸버이다. 요청 콘텐츠 형식의 기반을 두어 선택한 하나 이상의 다른 뷰 리졸버에게 위임하는 역할을 한다.

 

[ BeanNameViewResolver ]

 

뷰 이름과 동일한 이름을 갖는 빈을 뷰 객체로 사용하는 뷰 리졸버이다. 주로 커스텀 뷰 클래스를 뷰로 사용해야 할 때 사용된다. 이 방법은 뷰 이름과 빈 이름이 일치해야 한다는 제약이 존재한다. 따라서 뷰 이름과 일치하는 이름을 가진 빈이 하나만 존재하도록 보장해야 한다.

 

[ ViewResolverComposite ]

 

스프링 프레임워크에서 뷰 리졸버를 묶어서 처리하는 클래스이다. 단순히 뷰 리졸버를 하나만 사용할 때는 사용할 필요가 없지만, 여러 개의 뷰 리졸버를 등록할 때 사용된다.

 

[ InternalResourceViewResolver ]

 

스프링의 뷰 리졸버 중 하나로 주로 JSP를 사용할 때 쓰이는 뷰 리졸버이다. 뷰를 생성할 필요 없이 논리적인 이름만을 반환해 주면 된다. prefix와 suffix 프로퍼티를 이용해 앞뒤에 붙는 내용을 생략할 수 있다.

뷰 이름을 반환하지 않아도 가능한가

결론부터 말하자면 가능하다. 정확히 말하면 반환값이 뷰 이름이 아니더라도 존재하는 뷰 이름을 ModelAndView에 담으면 가능하다

thymeleaf 의존성을 추가한 것으로 가정하고 설명을 진행한다. 타임리프 의존성을 추가하면 ThymeleafViewResolver가 목록에 추가된다.

@GetMapping("/view/layout")
public String layout() {
	return "layout";
}

따라서 위의 경로로 요청하게 되는 경우 타임리프 뷰 리졸버에 의해 /resources/templates/layout.html경로에 파일이 존재하면 해당 뷰를 반환하게 된다. 하지만 경로에 파일이 존재하지 않으면 앞서 설명한 대로 예외가 발생하게 된다.

@GetMapping("/view/layout")
public ViewDto layout() {
	return new ViewDto(1L, "layout");
}

반환값을 Long, String 타입의 필드를 가지는 객체로 바꾼다면 아래의 사진과 같이 뷰 이름은 view/layout가 되고 객체는 모델 정보로 매핑된다.

그렇다면 /resources/templates/view/layout.html경로에 파일이 존재하면 해당 뷰를 반환할 수 있게 되는 것이다. 또한 해당 뷰가 모델에 사용되는 객체의 정보를 사용할 수도 있다.

@GetMapping("/view/layout")
public void layout() {
}

마찬가지로 반환값이 존재하지 않더라도 /resources/template/view/layout.html경로에 파일이 존재하면 뷰를 반환할 수 있다.

댓글
최근에 올라온 글
최근에 달린 댓글
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Total
Today
Yesterday