MisoBoy Blog...

[토비스프링] 12장 스프링 웹기술과 스프링 MVC 본문

Spring framework

[토비스프링] 12장 스프링 웹기술과 스프링 MVC

misoboy 2012. 9. 18. 11:44

스프링 MVC에서 V에 대해 정리했습니다.

스프링의 MVC 프로세서 중 컨트롤러의 결과 리턴: 모델과 뷰 (p.1025)를 이해하고 View에 대해 살펴보는게 좋을 것 같습니다.

 컨트롤러가 뷰 오브젝트를 직접 리턴할 수도 있지만, 보통은 뷰의 논리적인 이름을 리턴해주면 DispathcherServlet의 전략인 뷰 리졸버가 이를 이용해 뷰 오브젝트를 생성해준다.  대표적으로 사용되는 뷰는 jsp/jstl 뷰다. jsp파일로 만들어진 뷰 템플릿과 JstlView클래스로 만들어진 뷰 오브젝트가 결합해서 최종적으로 사용자가 보게 될 HTML을 생성하는데, 이 경우 컨트롤러는 JstlView가 사용할 jsp템플릿 파일의 이름을 리턴해줘야한다. 뷰를 사용하는 전략과 방법도 매우 다양하기 때문에, 일단은 컨트롤러가 뷰에 대한 정보를 돌려준다.

컨트롤러가 러턴해주는 정보는 결국 모델과 뷰다.  스프링에서는 ModelAndView라는 이름의 오브젝트가 있는데, 이 ModeAndView가 DispatcherServlet이 최종적으로 어댑터를 통해 컨트롤러로부터 돌려 받는 오브젝트다. 이름 그대로 모델과 뷰, 두가지 정보를 담고있다.
모델과 뷰를 넘기는 것으로 컨트롤러의 책임은 끝이다. 다시 작업은 DispatcherServlet으로 넘어간다.

DispatcherServlet이 컨트롤러로부터 모델과 뷰를 받은 뒤에 진행하는 작업은 뷰 오브젝트에게 모델을전달해주고 클라이언트에게 돌려줄 최종 결과물을생성해달라고 요청한다. jsp를 이용해서 결과물을 만들어주는 JstlView는 컨트롤러가 돌려준 jsp뷰 템플릿의 이름을 가져다 HTML을 생성하는데, 그중 동적으로 생성되도록 표시된 부분은 모델의 내용을 참고로 해서 내용을 채워준다. 
뷰 생성후 DispatcherServlet은 후처리가 있는지 확인후 그에 따라 진행된후 서블릿 컨테이에게 돌려주고 서블릿 컨테이너는 HttpServetResponce에 담긴 정보를 HTTP응답으로 만들어 사용자 브라우저에 전송후 종료한다.




간단히 정리하자면  컨트롤로에서 뷰 오브젝트나 , 뷰 리졸버를 이용한 뷰 이름과 model을 DispatcherServelt으로 넘기면 다시 DispatcherServlet은 해당 뷰를 처리하는  클래스에 넘겨주면 
해당 클래스는 결과물을 생성해서 DispatcherServlet에게 돌려주고 최종적으로 브라우저에게 전송후 종료한다.

 

컨트롤러가 작업을 마친 후 뷰 정보를 ModelAndView 타입 오브젝트에 담아서 DispatcherServelt에 돌려주는 방법은 두가지가 있다.


1. View 타입의 오브젝트를 돌려주는 방법
2. 뷰 이름을 돌려주는 방법
   - 뷰 이름을 돌려주는 경우는 뷰 이름으로부터 실제사용할 뷰를 결졍해주는 뷰 리졸버가 필요하다.




1. 뷰 오브젝트의 종류 - View인터페이스의 구현체

뷰 오브젝트의 종류

InternalResourceView, JstlView, RedirectView, 
VelocityView, FreeMarkerView , MarshallingView(3.0) 
AbstractExcelView, AbstractJeCelView, AbstractPdfView
AbstractAtomFeedView, AbstractRssFeedView
XsltView, TilesView, AbstractJasperReportsView
MappingJacksonJsonView.



모두 View인터페이스를 구현했다.



view 인터페이스 

public interface View {
 
String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus"; 
String getContentType();  // 뷰오브젝트가 생성하는 콘텐트의 타입 정보를 제공
void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse       response) throws Exception;  // 모델을 전달받아 클라이언트에 돌려줄 결과물을 만들어주는 메소드
}


뷰를 사용하는 방법

1. 스프링이 제공하는 기반 뷰 클래스를 확장해서 코드로 뷰를 만드는 방법
    ex) 엑셀, PDF, RSS 피트와 같은 뷰는 콘텐트를 생성하는 API를 사용해서 뷰 로직 생성
2. 스프링이 제공하는 뷰를 활용하되 뷰 클래스 자체를 상속하거나 코드를 작성하지는 않고, jsp나 프리마터같은 템플릿 파일을 사용하거나 모델을 자동으로 뷰로 전환하는 로직을 적용하는 방법이다.




각 뷰 오브젝트 살펴보기

뷰 오브젝트들의 종류와 기능을 살펴보자.

먼저 우리에게 익숙한  RequestDispatcher의 forward()와 include를 이용하는*InternalResourceView

-- RequestDispatcher --
req.setAttribute("message",message);
req.getRequestDispatcher("/jsp/hello.jsp").forward(req,res);


 컨츠롤러가 돌려준 뷰 이름을 포워딩할 jsp의 이름으로 사용하고 모델 정보를 요청 애트리뷰트에 넣어주는 작업을 InternalResourceView와 DispatcherServlet이 대신 해주는 것 뿐이다.


public ModelAndView notice(){
Map<String, Object> model=new HashMap<String, Object>();
model.put("message", "안녕~!!"); 
View view=new InternalResourceView("/jsp/hello.jsp");// forward
View view=new InternalResourceView("/jsp/hello.jsp",true);// include 
return new ModelAndView(view,model); 
}


JstlView 뷰 : extends InternalResoureView

*JstlView 이점:
 지역화 지원 

public ModelAndView jstlNotice(){
Map<String, Object> model=new HashMap<String, Object>();
model.put("message", "안녕~!!"); 
//View view=new JstlView(url);
//View view=new JstlView(url, MessageSource); //지역화기능
View view=new JstlView("/notice.jsp");
return new ModelAndView(view,model); 
}


 * 뷰리졸버(InternalResourceViewResolver)통한 간결한코드 유연한 기능


*RedirectView
  HttpServletResponse의 sendRedirect를 호출해주는 기능을 가진 뷰


public ModelAndView redirect(HttpServletRequest request, HttpServletResponse response){
RedirectView redirectView= new RedirectView("/notice", true);
or
RedirectView redirectView= new RedirectView("/notice");
// 웹 애플리케이션의 루트패스가 /가 아니라면
redirectView.setContextRelative(true);
return new ModelAndView(redirectView);
 or
return new ModelAndView("redirect:/notice");
}




* VelocityView, FreeMarkerView

* jsp와 벨로시티, 프리마커의 차이점(이점)
장점-
  1. 문법이 훨씬 강력하고 속도가 빠른 템플릿 엔진을 사용
   - 문법이 강력하다고하는데 벨로시티를 조금사용해봤는데 익숙하지 않아서라기보다 좀 부족한 기능       이 있었던 기억이... 프리마커는 완전 모르겠슴.
 2. 독립적인 템플릿 엔진으로 뷰를 생성하기 때문에 단위테스트 가능 그에 반해 jsp는 서버의 jsp서블릿   을 구동시켜야 동작
단점-
  학습필요, 툴기능 지원 부족
  
※컨트롤로에서 직접 뷰 오브젝트를 만드는 대신 VelocityViewResolver와 FreeMarkerViewResolver를 통해 자동으로 뷰가 만들어져 사용되게하는게 낫다.

*MarshallingView : spring 3.0 
OXM -object-xml Mapping 추상화 기능을 활용해서 application/xml 타입의 XML콘텐트를 작성하게주는 뷰

마샬러 빈을 지정하고 모델에서 변환에 사용할 오브젝트를 지정해주면, OXM 마샬러를 통해 모델 오브젝트를 xml로 변환해서 뷰의 결과로 사용할 수 있다.

따라서 마샬러 빈을 지정해주고 오브젝트를 지정해줘야한다.
마샬러빈은 JAXB2, JiBX, XmlBeans, Castor, XStream 있다.

이 중 책 예제는 Castor를 사용해 마샬러 빈을 구현했다.

 

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<bean id="marshaller" class="org.springframework.oxm.castor.CastorMarshaller"></bean>
<bean id="helloMarshallingView" class="org.springframework.web.servlet.view.xml.MarshallingView">
<property name="marshaller" ref="marshaller"></property>
<property name="modelKey" value="info"></property> 
</bean>
</beans>



@Resource MarshallingView marshallingView;
public ModelAndView helloMarshallingView(){
Map<String, Object> model=new HashMap<String, Object>();
model.put("info",new Info("Hello","wishvoice"));  
return new ModelAndView(marshallingView,model); 
}


장점:
 스프링 OXM의 추상화 기능을 활용할 수 있으므로 별도의 코드를 작성하지 않아도 손쉽게 모델에 담긴 오브젝트 정보를 XML로 변환할 수 있는데다 OXM 기술과 매핑정보도 자유롭게 변경 가능하므로 XML콘텐트를 클라이언트에 보내야하는 경우에 유용하다.

* OXM 기술을 적용해서 XML콘텐트를 만드는 방법은 애노테이션 기반의 MVC 컨트롤러를 이용해 마샬링 뷰 대신 메시지 컨버터로 XML결과를 만드는 방법(13장)이 있다.




용어: OXM
Object/XML Mapping, 줄여서 O/X mapping은 Object를 XML문서로 변환하는데 이를 XML Mashalling 또는Marshalling 이다. 반대로 XML문서를 Object로 변환하는 것은 Unmarshalling



*AbstractExcelView, AbstractJeCelView, AbstractPdfView : 엑셀, PDF
이름에서 알 수 있듯이 엑셀과 PDF문서를 만들어 주는 뷰다.
AbstractExcelView - 아파치 POI API
AbstractJeCelView - JExcelAPI
AbstractPdfView    - iText프레임워크 API

이 세 개의 뷰를 상속해서 개별적인 뷰를 만든다. 템플릿 패턴이 적용되어 있기 때문에 적절한 훅 -hook- 메소드를 오버라이드해주는 방법으로 문서 생성 코드를 추가할 수있다.
엑셀과 PDF 모두 미리 만들어둔 템플릿을 읽어와 일부 내용만 변경해서 문서를 완성할 수도 있다.

PDF문서 뷰의 모든 내용을 코드로 만드는 대신 이미 존재하는 PDF 문서에 AcroForm을 이용해 내용넣을 수도 있다. 이때는  AbstractPdfView대신 AbstractPdfStamperView를 사용해야한다.

코드


Template Method 
1. 컨텍스트
일부 단계를 하위 클래스에서 구현하게 하고오퍼레이션 알고리즘의 뼈대(skeleton)만을 정의하는 패턴. Template Method는 하위 클래스가 알고리즘의 구조는 변경하지 않고, 알고리즘의 특정 단계를 재정의하는 것을 가능하게 한다.
 
2. 적용 영역
l        변하지 않는 알고리즘은 한번만 구현하고, 다양한 변이가 존재하는 행위들에 대해서는 하위 클래스가 이를 구현하도록 유도한다.
l        하위 클래스 사이의 공통적인 행위들을 추출하여 공통적인 클래스를 정의하면, 코드의 중복을 막을 수 있다.
l        하위 클래스의 확장을 통제하기 위해 Template Method를 사용할 수 있다. 이러한 경우에는 Template Method를 후크(hook) 오퍼레이션이라고 부른다.

 참조: http://blog.naver.com/swinter8?Redirect=Log&logNo=130001814403 




*AbstractAtomFeedView, AbstractRssFeedView

이 뷰는 각각 application/atom+xml과 application/rss+xml타입의 피드문서를 생성해주는 뷰다.

*XsltView, TilesView, AbstractJasperReportsView

XsltView는 XSLT변환을 이용해 뷰를 생성해준다.
TilesView는 Tiles1,2를 이용해 뷰를 생성
AbstractJsperReportView는 리포트 작성용 프레임워크인 JasperReports를 이용해 CSV, HTML, PDF, Excel 형태의 리포트를 작성해준다.

*MappingJacksonJsonView
 AJAX에서 많이 사용되는 JSON타입의 콘텐트를 작성해준는 뷰다.


-- 코드는 일부만 작성한거입니다. Controller를 상속하지 않았습니다.


2.뷰 리졸버 : 뷰 이름으로부터 뷰 오브젝트를 찾아주는 
 

API: http://static.springsource.org/spring/docs/3.0.x/api/index.html
참고 : 
http://toby.epril.com/?p=980&cpage=1&replytocom=30103
         
http://openframework.or.kr/framework_reference/spring/ver2.x/html/mvc.html#mvc-coc-r2vnt
 
 * 컨트롤러에서 뷰 오브젝트를 리턴해주는 첫번째 방법은 컨트롤러가 특정 뷰에 종속적이게된다.
이를 보안해주는 뷰 리졸버 - 뷰 이름으로부터 사용할 뷰 오브젝트를 찾아주는 뷰 리졸버에 대해 알아보자.
 뷰 리졸버는 ViewResolver 인터페이스를 구현해서 만들어진다.뷰 리졸버를 빈으로 등록하지 않는다면 DispatcherServlet의 디폴트 뷰 리졸버인 InternalResourceViewResolver가 사용된다.
핸들러 매핑과 마찬가지로 뷰 리졸버도 하나 이상을 빈으로 등록해서 사용가능, order프로퍼티 이용


리졸버의종류와 기능을 살펴보자.

종류

InternalResourceViewResolver

VelocityViewResolver, FreeMarkerViewSesolver

ResourceBundleViewResolver, XmlViewResolver, BeanNameViewResolver

ContentNegotiatingViewResoler



InternalResourceViewResolver : 디폴트

InternalResourceView(예를 들면 서블릿과 JSP)와 JstlViewTilesView와 같은 하위 클래스를 지원하는 UrlBasedViewResolver의 편리한 하위 클래스. 이 결정자에 의해 생성되는 모든 view를 위한 view클래스는 setViewClass를 통해 정의될수 있다. 상세사항을 위해UrlBasedViewResolver's의 JavaDoc를 보라.
 
주로 jsp를 뷰로 사용하고자 할때 사용하나 그대로 사용하는 일은 피해야한다.
디폴트 상태의 InteranlResourceviewResolver를 사용할 경우 /jsp/view/hello.jsp를 뷰로 이용하려면전체 경로를 다 적어줘야하지만 prefix, suffix 프로퍼티를 이용해 앞뒤 내용을 생략할 수 있다.

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">     
        <property name="prefix" value="/jsp/" />
        <property name="suffix" value=".jsp" />
        <property name="order" value="0"/>
</bean>

prefix,suffix는 생략가능하고 이 프로퍼티의 이점은 jsp에 종속되지 않아 추후 벨로시티.vm, 프리마커.flt 뷰로 전환해도 문제 없다.

*RequestToViewNameTranslator 


public class RegistrationController implements Controller {                    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) {        
// process the request...        ModelAndView mav = new ModelAndView();       
 // add data as necessary to the model...        
return mav;        
// notice that no View or logical view name has been set   
 }
}






<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"        "http://www.springframework.org/dtd/spring-beans-2.0.dtd"><beans>    <!-- this bean with the well known name generates view names for us -->    
<bean id="viewNameTranslator" class="org.springframework.web.servlet.view.
DefaultRequestToViewNameTranslator"/> 

<bean class="x.y.RegistrationControllerController">
       
<!-- inject dependencies as necessary -->  
 
</bean>        
<!-- maps request URLs to Controller names -->    
<bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/> 

<bean id="viewResolver" class="org.springframework.web.servlet.view.
 
InternalResourceViewResolver">
 
<property name="prefix" value="/WEB-INF/jsp/"/>       
 <property name="suffix" value=".jsp"/>    

</bean>
</beans> 




반환된 ModelAndView에 셋팅되는 View 나 논리적인 view이름은 없다. 이것은 요청 URL로부터 논리적인 view명를 생성할 DefaultRequestToViewNameTranslator이다.ControllerClassNameHandlerMapping와 함께 사용되는 위 RegistrationControllerController의 경우, 'http://localhost/registration.html' 요청 URL은 DefaultRequestToViewNameTranslator에 의해 생성된 'registration'의 논리적인 view명의 결과가 된다. 논리적인 view명은InternalResourceViewResolver bean에 의해 '/WEB-INF/jsp/registration.jsp' view로 해석될것이다.


Tip

당신은 DefaultRequestToViewNameTranslator bean을 명시적으로 정의할 필요가 없다. 당신이 DefaultRequestToViewNameTranslator의 디폴트 셋팅을 승낙한다면, 명시적으로 설정되지 않았을때 Spring 웹 MVC DispatcherServlet이 클래스의 인스턴스를 인스턴스화할것이라는 사실에 의존할수 있다.

물론, 당신이 디폴트 셋팅을 변경할 필요가 있다면, 당신은 자체적인 DefaultRequestToViewNameTranslator bean을 명시적으로 설정할 필요가 있다. 설정될수 있는 다양한 프라퍼티의 상세한 설명을 위해서 DefaultRequestToViewNameTranslator 클래스를 위한 편리한 Javadoc을 보라.

13.12. 더많은 자원

Spring 웹 MVC에 대한 더 많은 자원을 위한 링크와 위치를 아래에서 보라.

  • Spring 배포판은 단계별 접근법을 사용하여 완전한 Spring 웹 MVC-기반의 애플리케이션을 빌드하여 독자를 가이드하는 Spring 웹 MVC 튜토리얼을 포함한다. 이 튜토리얼은 Spring 배포판에서 'docs' 디렉토리에 있다. 온라인 버전은 Spring 프레임워크 웹사이트에서 찾을수 있다..

  • Seth Ladd와 다른 사람들에 의해 만들어진 “Expert Spring Web MVC and WebFlow” 책은 Spring 웹 MVC 의 완벽한 하드카피 소스이다.

 
 * copy & paste 했습니다. ㅎㅎ
 


*VelocityViewResolver, FreeMarkerViewSesolver

InternalResourceViewResolver과 비슷하나 템플릿의 경로를 만들 때 사용할 루트 패스를 미리 VelocityConfigrer나 FreeMarkerConfigurer로 지정해줘야한다. 그래서 prefix는 잘 사용하지 않는다.


Velocity config

<bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer"> 
<property name="resourceLoaderPath" value="/WEB-INF/velocity/"/>
</bean>
<bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">  
<property name="cache" value="true"/>  
<property name="prefix" value=""/>  
<property name="suffix" value=".vm"/>   
<!-- if you want to use the Spring Velocity macros, set this property to true -->  
<property name="exposeSpringMacroHelpers" value="true"/>
</bean>



Freemarker config

<!-- freemarker config -->
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">  
<property name="templateLoaderPath" value="/WEB-INF/freemarker/"/>
</bean>
 <bean id="viewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">  
 <property name="cache" value="true"/>  
 <property name="prefix" value=""/> 
 <property name="suffix" value=".ftl"/>       
 <property name="exposeSpringMacroHelpers" value="true"/>
</bean>



*ResourceBundleViewResolver


컨트롤러마다 뷰의 종류를 달리할 경우 사용
views.properties파일에 클래스와 view를 매핑해놓고 매칭된 뷰를 호출해 다양한 뷰를 사용할 수 있다. 비권장 - 너무 손이 많이간다. 지역화 기능 제공



*XmlViewResolver

ResourceBundleViewResolver 와 비슷하다. 단지 프로퍼티 파일 대신 xml의 빈 설정파일을 이용해 뷰를 등록할 수 있게 해준다.빈 설정을 이용하기 때문에 프로퍼티 파일과 달리 DI를 자유롭게 이용할 수 있다는 장점이 있다.
단점: 지역화 기능 미제공. ResourceBundleViewResolver 와 달리
 

*BeanNameViewResolver
 뷰 이름과 동일한 빈 이름을 가진 빈을 찾아서 뷰로 사용하게 해준다. 서블릿 컨텍스트의 빈을 사용한다.
 

*미디어 타입으로 view 선택(?) 3.0 :  ContentNegotiatingViewResoler - 뷰 리졸버를 결정해주는 리졸버


  미디어 타입 정보를 활용해서 다른 뷰 리졸버에게 뷰를 찾도록 위임한 후에 가장 적절한 뷰를 선정해서 돌려준다~!!!
 
그럼 다양한 뷰를 결정하는 과정을 살펴보자!

미디어 타입에 따른 뷰를 결정하는 순서는 

미디어 타입 결정  ===> 타입에 따른 뷰 리졸버 위임을 통한 후보 뷰 선정 ===> 미디어 타입 비교를 통한 최종 뷰 선정



그럼 하나하나 살펴보자

1. 미디어 타입 결정 

미디어 타입은 HTTP의 콘텐트 타입에 대응된다.  html, pdf, atom, xml, json등으로 콘텐트 타입을 표현.
이런 타입을 결정하는 방법은 ?

  미디어 타입을 결정하는 4가지 방법

  첫째 . URL의 확장자를 사용하는 방법 : ContentNegotiatingViewResolver 디폴트 설정

  두번째. 첫째에서 결정하지 못했다면 . 포맷을 지정하는 파라미터로부터 미디어 타입을 추출하는 방법 미디어 타입 파라미터를 이용하려면 ContentNegotiatingViewResolver의 favorParameter 프로퍼티를 true로 설정해줘야한다. /hello?form=pdf라면 pdf 미디어 타입이라고 해석함

셋번째. 위 두가지 방법이 적용되지 않거나 적용됐더라도 원하는 미디어 타입을 찾지 못했다면. Http의 콘텐트 교섭에 사용되는 Accept 헤더의 설정을 이용하는 방법
  문제는 HTML에서는 간단히 Accept헤더를 설정할 수 있는 방법이 없다는 점. 어쨌든. Agent헤더를 지정할 방법이 있다면 ignoreAcceptHeader를 false로 설정해주면된다.

네번째 방법: 앞의 모든 방법으로도 찾지 못했을 경우에 적용된다. 이때는 defaultContentType 프로퍼티에 설정해준 디폴트 미디어 타입을 사용한다.

ContentNegotiatingViewResolver에서 사용할 수 있는 미디어 타입은 미리 mediaTypes프로퍼티에 등록해놔야 한다. 미디어 타입을  URL확장자 등에서 추출하고 나면 mediaTypes에 등록된 것인지 확인한다. 

<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="mediaTypes">        
<map>            
<entry key="html" value="text/html"/>            
<entry key="atom" value="application/atom+xml"/>            
<entry key="json" value="application/json"/>        
</map>   
</property>      
<property name="viewResolvers">        
<list>            
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver"/>            
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">                
<property name="prefix" value="/WEB-INF/jsp/"/>                
<property name="suffix" value=".jsp"/>            
</bean>        
</list>    
</property>       
<property name="defaultViews">        
<list>            
<bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView"/>        
</list>    
</property>
</bean>


* 미디어 타입 방법이 4가지가 있다는게 중요한게 아니라 4가지의 적용되는 순서가 더 중요한듯하다.
 

2. 뷰 리졸버 위임을 통한 후보 뷰 선정

 미디어 타입이 결정됐다면 다음은 적용 가능한 뷰 후보를 찾는다. 컨트롤러가 돌려준 뷰 이름을 등록된 모든 뷰 리졸버에게 보내서 사용 가능한 뷰를 확인하는 방법을 사용한다.
뷰 후보 선정에 사용할 뷰 리졸버는 viewResolvers 프로퍼티를 이용해 지정해줄수 있다.
<property name="viewResolvers"> 

후보 뷰 선정- 몇가지 tip
* viewResolvers 프로퍼티에 뷰 리졸버를 등록하지 않았다면, 서블릿 컨텍스트에 등록된 ViewResolver 타입의 빈을 모두 찾아서 사용한다.
* 여러개의 뷰 리졸버를 사용하지만 우선순위는 무시한다.
* 뷰 리졸버를 거치지 않고 특정 뷰를 선정 대상에 넣고 싶다면 defaultViews에 뷰를 추가하면된다. 


3. 미디어 타입 비교를 통한 최종 뷰 선정
  뷰리졸버 후보를 찾았다면 이제는  요청정보에서 가져온 미디어 타입과 뷰 리졸버에서 찾은 후보  뷰 목록을 매칭해서 사용할 뷰를 결정한다.

예를 들어 컨트롤러가 돌려준 뷰 이름으로 모든 뷰 리졸버에 조회했더니 HTML, PDF, Excel 미디어 타입의 뷰 후보를 얻을 수 있었다고해해보자. 이 중에서 요청정보에서 추출한 미디어 타입과 일치하는 것이 최종적으로 사용할 뷰가 된다. 만약 URL이 .html로 끝났다면 첫 번째 단계에서 HTML미디어타입으로 결정됐을 것이다. 그렇다면 HTML미디어 타입을 가진 후보 뷰가 최종 사용할 뷰로 선정될 것이다.

즉, 사용자가 요청한 미디어 타입, 컨트롤러가 돌려준 뷰 이름, 뷰 리졸버에 등록된 뷰의 조합을 통해 뷰가 결정되는 것이다.
이점: 보통 다양한 뷰를 선택 하려면 맞는 컨트롤러를 여러개 만들거나 컨트롤러에서 조건에따른 뷰 선택 로직을 넣어야하지만 ContentNegotiatingViewResolver은 URL의 확장자를 이용하거나 포맷 파라미터를 지정해서 뷰의 종류를 선택하게할 수 있다.

ContentNegotiatingViewResolver사용 참고내용
막강한 기능을 갖고 있는 ContentNegotiatingViewResolver. 사용할 뷰의 종류와 지원할 미디어 타입을 결정하는 것이 중요하다. 그리고 그에 따라 컨트롤러가 리턴할 뷰 이름의 패턴을 결정해두고 이를 처리하는 뷰 리졸버를 구성한다. 마지막으로 미디어 타입 지정방식을 결정하고 일괄 적용할 디폴트 뷰를 선정한다.