본문 바로가기
  • 실행력이 모든걸 결정한다
Spring Series/Spring Framework

[Spring] View의 종류와 다양한 전략

by 김코더 김주역 2022. 7. 22.
반응형

1. View의 종류

1) InternalResourceView

return new ModelAndView("/WEB-INF/view/home.jsp", model);

- RequestDispatch의 forward() 또는 include()를 이용하는 방법이다.

- 주로 JSP 서블릿을 통해 JSP 뷰를 작성할 때 사용한다.

- InternalResourceView의 서브클래스인 JstlView는 지역정보(Locale)에 따라 달라지는 지역화된 메시지를 JSP 뷰에 출력할 수 있게 해준다.

※ 지역정보가 한국으로 설정되어 있다면 messages_ko.properties 파일의 내용 JSTL의 <fmt:message> 태그로 출력할 수 있다.

 

 

2) RedirectView

return new ModelAndView(new RedirectView("/info", true)); // true로 설정하면 context path 기준의 상대 경로가 사용된다.
// or
return new ModelAndView("redirect:info");

- HttpServletResponse의 sendRedirect()를 이용하는 방법이다.

- 이름 그대로 다른 페이지로 리다이렉트할 때 사용된다.

 

 

3) 템플릿 엔진을 이용하는 View

- Velocity나 FreeMarker처럼 독립적인 템플릿 엔진으로 뷰를 생성하기 때문에 단위 테스트에 유리하다.

- 표준 기술인 JSP에 비해 확장성, 생산성, 테스트 편의성, 성능이 좋을 순 있어도 IDE의 충분한 지원을 받지 못할 수 있다.

 

 

4) MarshallingView

- OXM(Object-XML Mapping) 추상화를 활용하여 application/xml 컨텐츠를 편리하게 작성하게 해준다.

- 아래와 같이 각자 선택한 마샬링 bean과 xml로 변환할 객체의 모델 키를 지정해주고,

<bean id="homeMarshallingView" class="org.springframework.web.servlet.view.xml.MarshallingView">
    <property name="marshaller" ref="marshaller">
    <property name="modelKey" value="member">
</bean>

<!--원하는 마샬러 라이브러리를 미리 다운받고 bean으로 등록-->
<bean id="marshaller" class="org.springframework.oxm.castor.CastorMarshaller" />

컨트롤러에서 model에 모델 키와 객체를 추가해주면 된다.

Map<String, Object> model = new HashMap<String, Object>();
Member member = new Member();
...
model.put("member", member);
return new ModelAndView(homeMarshallingView, model); // 마샬링 뷰 bean을 뷰로 반환

 

 

5) AbstractExcelView, AbstractJExcelView, AbstractPdfView

- AbstractExcelView는 Apache POI 라이브러리를 이용해서 엑셀 뷰를 만들어주고, AbstractJExcelView는 JExcelAPI를 이용해서 엑셀 문서를 만들어주고, AbstractPdfView는 iText 프레임워크 API로 PDF 문서를 생성해준다.

- 멀티스레드 환경에서 공유해서 사용해도 안전하다.

- 아래 예시와 같이, 상속이 필요하며 적절한 훅 메소드를 오버라이드해서 문서 생성 코드를 추가하면 된다.

public class myPdfView extends AbstractPdfView {
    protected void buildPdfDocument(Map<String, Object> model, Document document, PdfWriter pdfWriter,
        HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 구현   
    }
}

- 등록한 뷰 bean은 컨트롤러로 가져와서 아래와 같이 뷰로 반환하면 된다. 

return new ModelAndView(myPdfView, model);

 

 

6) AbstractAtomFeedView, AbstractRssFeedView

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

- 이 뷰 역시 상속을 통해 피드 정보를 생성하는 적절한 훅 메소드를 구현하고, 뷰 bean을 컨트롤러에서 뷰로 반환하면 된다.

※ AbstractAtomFeedView의 훅 메소드 : buildFeedEntries

※ AbstractRssFeedView의 훅 메소드 : buildFeedItems

 

 

7) XsltView, TilesView

- XsltView는 XSLT 변환을 통해 뷰를 생성해주며, javax.xml.transform.TransformerFactory 타입의 팩토리 클래스와 적용할 모델 오브젝트를 지정해줘야 한다.

- TilesView는 Tiles 1, 2를 이용해서 뷰를 생성한다.

 

 

8) AbstractJasperReportsView

- 레포트 작성용 프레임워크인 JasperReports를 이용해서 HTML, CSV, PDF, Excel 형식의 레포트를 작성해준다. 각각 JasperReportsHtmlView, JasperReportsCsvView, JasperReportsPdfView, JasperReportsXlsView를 사용할 수 있다.

 

 

9) MappingJacksonJsonView

- JSON 타입의 콘텐트(application/json)를 작성해주는 뷰다.

- Jackson JSON 프로세서를 통해 모델 오브젝트를 JSON 타입으로 변환해준다.

- Set<String> 타입인 renderedAttributes 속성을 통해 일부 모델 오브젝트들만 JSON 변환을 할 수도 있다.

 

 

 

2. 뷰 리졸버의 종류

- 뷰 리졸버는 뷰 이름으로부터 사용할 뷰 오브젝트를 찾아주는 역할을 하며, ViewResolver 인터페이스를 구현해서 만들어진다.

- 하나 이상의 뷰 리졸버 bean을 사용한다면 order 속성을 이용해서 뷰 리졸버의 적용 우선 순위를 적용할 수도 있다.

 

1) InternalResourceViewResolver (기본)

- 뷰 리졸버를 별도로 지정하지 않았을 때 자동 등록되는 뷰 리졸버다.

- prefix, suffix 속성을 이용하여 뷰의 전체 경로의 앞 부분과 뒷 부분을 명시해주면 컨트롤러에서 뷰를 반환할 때 중간 부분만 명시하면 된다.

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

※ 위의 경우에는 만약 컨트롤러의 핸들러 메소드가 "home"을 반환하면 "/WEB-INF/views/home.jsp"로 조합된다.

- JSTL 라이브러리가 클래스패스에 존재하면 JstlView를 사용하고, 존재하지 않으면 InternalResourceView를 사용한다.

 

 

2) VelocityViewResolver, FreeMarkerViewResolver

- 각각 템플릿 엔진 기반의 뷰인 VelocityView와 FreeMarkerView를 사용할 수 있게 해주는 뷰 리졸버다.

- InternalResourceViewResolver와 같이 prefix, suffix 속성을 사용할 수 있긴 하지만, 아래와 같이 반드시 설정 bean(Configurer)을 통해 템플릿의 루트 경로를 지정해줘야 하기 때문에 prefix는 굳이 쓸 필요가 없다.

<bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
    <property name="suffix" value=".vm" />
</bean>

<bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
    <property name="resourceLoaderPath" value="/WEB-INF/velocity/" />
</bean>

 

 

3) ResourceBundleViewResolver, XmlViewResolver, BeanNameViewResolver

- 컨트롤러마다 사용하는 뷰의 종류가 달라지는 경우에 사용하는 뷰 리졸버들이다.

 

(1) ResourceBundleViewResolver

- ResourceBundleViewResolver은 기본적으로 클래스패스의 views.properties 파일을 설정 파일로 인식한다. views.properties는 아래 예시와 같이 작성한다. 위 내용들을 모두 이해했다면 어렵지 않게 이해할 수 있을 것이다.

# views.properties
home.(class)=org.springframework.web.servlet.view.JstlView
home.url=/WEB-INF/view/home.jsp

info.(class)=org.springframework.web.servlet.view.velocity.VelocityView
info.url=info.vm

- 아래와 같이 우선순위(order) 속성를 이용하여 ResourceBundleViewResolver에 맞는 뷰가 없더라도 InternalResourceViewResolver가 처리하도록 하는 것이 좋다.

<bean class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
    <property name="order" value="0" />
</bean>

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" />

※ InternalResourceViewResolver의 order는 최대 정수값으로 지정되어 있기 때문에 우선순위가 최하위다.

- 뷰에서 다른 빈을 참조하는 경우에는 사용하기 어렵다는 단점이 있다.

 

(2) XmlViewResolver

- XmlViewResolver은 프로퍼티 파일 대신 XML bean 설정 파일을 이용해서 뷰를 등록할 수 있게 해준다.

- 서블릿 컨텍스트를 부모로 갖는 애플리케이션 컨텍스트로 만들어지기 때문에 DI를 자유롭게 이용할 수 있다.

- 뷰 이름과 일치하는 id를 가진 bean을 뷰로 사용한다.

- XmlViewResolver은 기본적으로 /WEB-INF/views.xml 파일을 참조한다.

 

(3) BeanNameViewResolver

- 뷰 이름과 일치하는 name을 가진 bean을 뷰로 사용한다.

- 별도의 뷰 전용 설정 파일을 두지 않고 서블릿 컨텍스트의 bean을 사용한다.

 

 

4) ContentNegotiatingViewResolver

- Spring 3.0에서 새로 추가된 뷰 리졸버다.

- 뷰 이름으로부터 뷰 오브젝트를 찾지 않고, 미디어 타입 정보를 활용해서 다른 뷰 리졸버에게 뷰를 찾도록 위임한다.

 

View 결정 과정

(i) 미디어 타입 결정

- 가장 먼저 사용자의 요청 정보로부터 미디어 타입 정보를 추출한다.미디어 타입은 html, pdf, xml, json 등으로 콘텐츠 타입을 표현한다.

- 미디어 타입을 결정하는 방법은 4가지가 있으며, 우선 순위는 아래로 갈수록 낮아진다.

  • URL의 확장자(기본값) : 파일 뒤에 붙는 확장자를 이용 - 예) /home.html, /home.json
  • 포맷을 지정하는 파라미터 : faverParameter 속성값을 true로 지정하고, URL의 format 파라미터를 미디어 타입으로 사용 - 예) /home?format=xml
  • Accept 헤더 설정 : ignoreAcceptHeader 속성값을 false로 설정해둬야 함
  • defaultContentType 속성값 : 위의 모든 방법으로도 미디어 타입을 찾지 못했을 때 적용됨

- 사용 가능한 미디어 타입들을 미리 mediaTypes 프로퍼티에 등록해놓아야 한다. 위의 방법들로 미디어 타입을 추출하고 나면 mediaTypes에 등록된 것인지 확인하고, 만약 없다면 JAF(JavaBeans Activation Framework)의 미디어 타입 정보를 이용해 미디어 타입을 확인한다.

<property name="mediaTypes">
    <map>
        <entry key="html" value="text/html"/>
        <entry key="json" value="application/json"/>
    </map>
</property>

 

(ii) 다른 뷰 리졸버에게 뷰를 찾도록 위임

- 미디어 타입이 결정된 후의 과정이다. 컨트롤러가 반환한 뷰 이름을 등록되어 있는 모든 뷰 리졸버에게 보내서 사용 가능한 뷰 후보들을 모은다. 즉, 뷰 리졸버의 우선순위(order)는 무시된다.

- 뷰 후보 선정에 사용할 뷰 리졸버는 viewResolvers 속성에 지정한다. viewResolvers 속성을 지정하지 않았다면, 서블릿 컨텍스트에 등록된 모든 ViewResolver 타입의 빈을 모두 찾아서 사용한다.

<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/views/" />
            <property name="suffix" value=".jsp" />
        </bean>
    </list>
</property>

※ ContentNegotiatingViewResolver를 사용하는 경우에는 다른 뷰 리졸버를 독립적으로 사용하지 않는다.

- 뷰 리졸버의 조회 결과에 상관 없이 특정 뷰를 무조건 후보 뷰 목록에 추가하고 싶다면 defaultViews 속성에 디폴트 뷰를 등록해주면 된다. 보통은 JSON이나 OXM 뷰를 디폴트 뷰로 등록해놓는다.

<property name="defaultViews">
    <list>
        <bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" />
    </list>
</property>

 

(iii) 미디어 타입 비교를 통한 최종 뷰 선정

- (i)에서 결정된 미디어 타입을 처리해주는 후보 뷰를 최종 뷰로 선정하는 과정이다.

 

 

 

3. 기타 DispatcherServlet 전략

1) 핸들러 예외 리졸버의 구현 전략

- HandlerExceptionResolver은 컨트롤러의 작업 중에 발생한 예외를 처리하는 전략이다.

- 핸들러 예외 리졸버가 등록되어 있다면 DispatcherServlet은 먼저 핸들러 예외 리졸버에게 해당 예외를 처리할 수 있는지 확인하고, 가능하다면 핸들러 예외 리졸버에게 예외 처리를 맡긴다.

- 핸들러 예외 리졸버는 HandlerExceptionResolver 인터페이스를 구현해서 만들 수 있다.

public interface HandlerExceptionResolver {
    ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);
}

이제 HandlerExceptionResolver의 4가지 구현 전략을 모두 알아보자.

 

(1) AnnotationMethodHandlerExceptionResolver

- Spring 3.0에서 새로 추가되었다.

- 컨트롤러 안에서 @ExceptionHanlder 어노테이션이 붙은 메소드를 찾아서 해당 메소드에게 예외 처리를 맡긴다.

@ExceptionHandler(DataAccessException.class) // DataAccessException 예외가 발생했을 때 호출되도록 한다.
public ModelAndView dataAccessExceptionHandler(DataAccessException ex) {
    return new ModelAndView("error_page").addObject("message", "ex.getMessage());
}

- 특정 컨트롤러의 작업 중에 발생하는 예외만 처리하는 경우에 편리한 방법이다.

 

(2) ResponseStatusExceptionResolver

- 예외를 특정 HTTP 응답 상태 코드로 전환해준다. 'HTTP Status 500' 에러 대신에 의미 있는 HTTP 응답 상태를 돌려줄 때 쓰인다.

- 예외 클래스에 @ResponseStatus를 붙이고, value 속성에는 새로 반환할 HTTP 응답 상태 값을 지정해주면 된다. 필요하다면 reason 속성에 설명을 추가할 수도 있다.

@ResponseStatus(value=HttpStatus.BAD_REQUEST, reason="잘못된 요청")
public class BadRequestException extends RuntimeException {}

- 기존에 정의된 예외 클래스에는 바로 적용할 수 없다. 기존 예외가 발생했을 때 HTTP 응답 상태를 지정해주려면 @ExceptionHandler 방식의 핸들러 메소드를 사용하면 된다. 예시는 https://midas123.tistory.com/189를 참고하자.

 

(3) DefaultHandlerExceptionResolver

- (1), (2)에서 처리하지 못한 예외를 다루는 핸들러 예외 리졸버다.

- Spring에서 내부적으로 발생하는 주요 예외를 처리해주는 표준 예외 처리 로직을 가지고 있다.

- 다른 핸들러 예외 리졸버를 빈으로 등록했다면 DefaultHandlerExceptionResolver가 자동으로 적용되지 않기 때문에, DefaultHandlerExceptionResolver를 직접 추가로 등록해주는 것이 좋다.

 

(4) SimpleMappingExceptionResolver

- 예외를 처리할 뷰를 직접 지정할 수 있게 해준다.

- exceptionMappings에는 예외와 그에 대응되는 뷰 이름을 등록해주면 되고, dafaultErrorView에는 exceptionMappings에서 찾을 수 없는 예외에 매핑할 뷰 이름을 등록해주면 된다.

<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="exceptionMappings">
        <props>
            <prop key="DataNotFoundException">error/dataNotFoundError</prop>
            <prop key="BusinessLogicException">error/businessLogicError</prop>
            <prop key="RuntimeException">error/runtimeError</prop>
        </props>
    </property>
    <property name="defaultErrorView" value="error/defaultError" />
</bean>

※ 그 외의 속성들은 https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/handler/SimpleMappingExceptionResolver.html문서를 참고하자.

- 디폴트 전략이 아니기 때문에 직접 bean으로 등록해야 한다.

- 모든 컨트롤러에서 발생하는 예외에 일괄 적용된다는 장점이 있다.

 

+ 보충) 예외가 발생했을 때 로그를 남기거나 관리자에게 통보하는 등의 작업이 필요하다면 핸들러 인터셉터의 afterCompletion() 메소드를 이용하자.

 

 

2) 지역 정보 리졸버

- LocaleResolver은 애플리케이션 전체에서 사용하는 지역정보(Locale)를 결정하는 전략이다.

- 기본적으로 사용되는 LocaleResolver은 AcceptHeaderLocaleResolver로, HTTP 헤더의 지역 정보를 사용한다.

※ HTTP 헤더의 지역 정보는 보통 브라우저의 설정에 따라 보내진다.

- SessionLocaleResolver나 CookieLocaleResolver를 사용해서 HTTP 세션이나 쿠키에 있는 지역 정보를 사용하게 할 수도 있다.

 

 

3) 멀티 파트 리졸버

- CommonsMultipartResolver은 파일 업로드와 같이 멀티 파트 포맷의 요청정보를 처리하는 전략으로, 아파치 Commons의 FileUpload 라이브러리를 사용한다.

- 디폴트로 등록되지 않기 때문에 bean을 등록해야 한다.

- maxUploadSize 속성을 이용해서 업로드 파일의 크기를 제안하는 것을 권장한다.

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="maxUploadSize" value="345600"/>
</bean>

- DispatcherServlet이 멀티 파트 요청을 받게 되면 멀티 파트 리졸버에게 요청해서 MultipartHttpServletRequest 오브젝트를 사용할 수 있게 해준다. 이렇게 되면 HttpServletRequest를 MultipartHttpServletRequest 타입으로 캐스팅해서 사용할 수 있다.

public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) {
    MultipartHttpServletRequest mRequest = (MultipartHttpServletRequest) request;
    MultipartFile mFile = mRequest.getFile("filename");
    ...
}

※ MultipartFile에서 사용할 수 있는 메소드 목록은 https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/multipart/MultipartFile.html문서에서 확인하면 된다.

 

 

4) RequestToViewNameTranslator

- 컨트롤러에서 뷰 이름이나 뷰 오브젝트를 반환하지 않았을 경우에 HTTP 요청 정보를 참고해서 뷰 이름을 생성해주는 전략이다.

- 기본 전략은 DefaultRequestToViewNameTranslator으로, 기본 URL에서 확장자를 제거한 부분을 뷰 이름으로 사용한다. 또, DefaultRequestToViewNameTranslator을 bean으로 직접 등록하고 prefix, suffix 속성을 설정함으로써 URL의 앞뒤에 접두사, 접미사를 붙여줄 수도 있다.

 

 

● 참고 자료 : 토비의 스프링 3.1 Vol.2

 

반응형

댓글