Spring Mvc 視圖解析

Spring Mvc 視圖解析html

在 Spring Mvc 中,咱們本身編寫的控制器方法(Controller) 並無直接去渲染結果,使用 response 去輸出到瀏覽器。方法返回的是 ModelAndView,甚至只是一個 String 類型的視圖名,那 Spring Mvc 是怎麼把模型數據填充到視圖的呢?若是控制器能經過邏輯視圖名來了解視圖的話,那Spring Mvc 如何肯定使用哪個視圖實現來渲染模型呢?java

1、瞭解視圖解析

一、解析過程

  • DispatcherServletweb

  • HandlerMappingspring

  • HandlerAdapterjson

  • ViewResolver瀏覽器

  • View緩存

對於控制器的方法,不管其返回值是 String、View、ModelMap 或是 ModelAndView,Spring MVC 都會在內部HandlerAdapter 將它們封裝爲一個 ModelAndView 對象再進行返回mvc

public interface HandlerAdapter {

    ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

    ...
}

而後 Spring MVC 會藉助視圖解析器 ViewResolver 獲得最終的視圖對象 Viewapp

public interface ViewResolver {
    //經過 viewname 解析 view
    View resolveViewName(String viewName, Locale locale) throws Exception;
}

View 接口表示一個響應給用戶的視圖如jsp文件,pdf文件,html文件。getContentType 方法會返回視圖的內容類型,render 方法接收模型以及 Servlet 的 request 和 response 對象,並將輸出結果渲染到 response 中jsp

public interface View {
    ...

    default String getContentType() {
        return null;
    }

    void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
}

二、ViewResolver 實現

下面是 Spring 中 ViewResolver 的一些實現

視圖解析器 描述
AbstractCachingViewResolver 一個抽象的視圖解析器類,提供了緩存視圖的功能。一般視圖在可以被使用以前須要通過準備。繼承這個基類的視圖解析器便可以得到緩存視圖的能力
XmlViewResolver 接受使用與 Spring 的XML bean工廠相同的DTD 編寫的 XML 配置文件。默認配置文件是 /WEB-INF/views.xml
ResourceBundleViewResolver 將視圖解析爲資源bundle(通常爲屬性文件)
UrlBasedViewResolver 直接根據視圖的名稱解析視圖,視圖的名稱會匹配一個物理視圖的定義
InternalResourceViewResolver UrlBasedViewResolver的子類,將視圖解析爲Web應用的內部資源(通常爲JSP)
FreeMarkerViewResolver UrlBasedViewResolver的子類,將視圖解析爲FreeMarker模板
ContentNegotiatingViewResolver 根據請求文件名或Accept標頭解析視圖,經過考慮客戶端須要的內容類型來解析視圖,委託給另一個可以產生對應內容類型的視圖解析器

2、視圖解析

一、重定向和轉發

  • redirect:
  • forward:

視圖名稱中使用前綴 redirect: 執行重定向,UrlBasedViewResolver(及其子類) 會認爲這是一條須要重定向的指令,視圖名稱的其他部分是重定向URL。若是是轉發的話,使用前綴 forward:

@RequestMapping("/index")
public String index(){
    return "redirect:index.jsp";
}

咱們能夠看一下 UrlBasedViewResolver 中的代碼

public class UrlBasedViewResolver extends AbstractCachingViewResolver implements Ordered {
    ... 
    @Override
    protected View createView(String viewName, Locale locale) throws Exception {

        if (!canHandle(viewName, locale)) {
            return null;
        }

        // Check for special "redirect:" prefix.
        if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
            String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
            RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
            String[] hosts = getRedirectHosts();
            if (hosts != null) {
                view.setHosts(hosts);
            }
            return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);
        }

        // Check for special "forward:" prefix.
        if (viewName.startsWith(FORWARD_URL_PREFIX)) {
            String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
            InternalResourceView view = new InternalResourceView(forwardUrl);
            return applyLifecycleMethods(FORWARD_URL_PREFIX, view);
        }

        // Else fall back to superclass implementation: calling loadView.
        return super.createView(viewName, locale);
    }
    ...
}

二、使用 JSP 視圖

有一些視圖解析器(如 ResourceBundleViewResolver)會直接將邏輯視圖名映射爲特定的View接口實現,而InternalResourceViewResolver 所採起的方式並不那麼直接。它遵循一種約定,會在視圖名上添加前綴和後綴,進而肯定一個 Web 應用中視圖資源的物理路徑

<bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"
    p:prefix="/WEB-INF/jsp/" 
    p:suffix=".jsp" />

InternalResourceViewResolver 配置就緒以後,它就會將邏輯視圖名解析爲 JSP 文件路徑。"index" 會被解析成 "/WEB-INF/jsp/index.jsp""blog/index" 會被解析成 "/WEB-INF/jsp/blog/index.jsp"

若是這些 JSP 使用 JSTL 標籤來處理格式化和信息的話,那麼咱們會但願 InternalResourceViewResolver 將視圖解析爲 JstlView

<bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"
    p:prefix="/WEB-INF/jsp/" 
    p:suffix=".jsp" 
    p:viewClass="org.springframework.web.servlet.view.JstlView" />

以下面的例子 "index" 會被解析成 "/WEB-INF/jsp/index.jsp"

@RequestMapping("/index")
public String index(String name,Model model){
    model.addAttribute("name",name);
    return "index";
}

三、Freemarker 模板視圖解析

添加 maven 依賴

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>${spring.version}</version>
</dependency>

<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.28</version>
</dependency>

FreeMarker 配置和解析器配置

<!-- FreeMarker 配置 -->
<bean id="freeMarkerConfigurer" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
    <property name="templateLoaderPath" value="/" />
    <property name="defaultEncoding" value="UTF-8" />
    <property name="freemarkerSettings">
        <props>
            <prop key="template_update_delay">10</prop>
            <prop key="locale">zh_CN</prop>
            <prop key="datetime_format">yyyy-MM-dd HH:mm:ss</prop>
            <prop key="date_format">yyyy-MM-dd</prop>
            <prop key="number_format">#.##</prop>
        </props>
    </property>
</bean>

<!-- FreeMarker視圖解析 -->
<bean id="freeMarkerViewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.freemarker.FreeMarkerView" />
    <property name="contentType" value="text/html;charset=UTF-8" />
    <property name="prefix" value="/ftl/" />
    <property name="suffix" value=".ftl" />
    <property name="exposeRequestAttributes" value="true" />
    <property name="exposeSessionAttributes" value="true" />
    <property name="exposeSpringMacroHelpers" value="true" />
</bean>

四、 Thymeleaf 模板視圖解析

  • ThymeleafViewResolver:將邏輯視圖名稱解析爲 Thymeleaf 模板視圖
  • SpringTemplateEngine:處理模板並渲染結果
  • TemplateResolver:加載Thymeleaf模板

添加 maven 依賴

<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf</artifactId>
    <version>3.0.11.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring5</artifactId>
    <version>3.0.11.RELEASE</version>
</dependency>

配置 Thymeleaf

<bean id="templateResolver" class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
    <property name="prefix" value="/templates/" />
    <property name="suffix" value=".html" />
    <property name="templateMode" value="HTML5" />
    <property name="cacheable" value="false" />
    <property name="characterEncoding" value="UTF-8"/>
</bean>

<bean id="templateEngine" class="org.thymeleaf.spring5.SpringTemplateEngine">
    <property name="templateResolver" ref="templateResolver" />
</bean>

<bean class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
    <property name="templateEngine" ref="templateEngine" />
    <property name="characterEncoding"  value="UTF-8" />
</bean>

ThymeleafViewResolver 是 Spring MVC 中 ViewResolver 的實現類。它把接收的邏輯視圖名稱解析一個 Thymeleaf 模板視圖

ThymeleafViewResolver bean 中注入了一個對 SpringTemplateEngine bean 的引 用。SpringTemplateEngine 會在 Spring 中啓用 Thymeleaf 引擎,用來解析模板,並基於這些模板渲染結果。

五、多視圖解析處理

當咱們同時使用多種視圖解析的時候,該如何解析呢?

這裏要分狀況討論了

1)多視圖解析處理

能夠聲明多個解析器,並在必要時經過設置 order 屬性指定排序。order 屬性值越高,視圖解析器在鏈中的位置越晚。

一個 ViewResolver 是能夠返回 null 的,表示沒法找到該視圖。若是一個視圖解析器不能返回一個視圖,那麼 Spring 會繼續檢查上下文中其餘的視圖解析器。此時若是存在其餘的解析器,Spring會繼續調用它們,直到產生一個視圖返回爲止。

可是 InternalResourceViewResolver 會執行調度 RequestDispatcher 來肯定 jsp 是否存在。所以,必須把 InternalResourceViewResolver 在視圖解析器鏈中的順序設置爲最後一個

<!--jsp 視圖解析-->
<!-- 這裏jsp的 order 要設置比freemarker的大,否則都會解析成 jsp -->
<bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"
    p:prefix="/jsp/"
    p:suffix=".jsp"
    p:viewClass="org.springframework.web.servlet.view.JstlView"
    p:order="1" />

<!-- FreeMarker 配置 -->
<bean id="freeMarkerConfigurer" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
    <property name="templateLoaderPath" value="/" />
    <property name="defaultEncoding" value="UTF-8" />
    <property name="freemarkerSettings">
        <props>
            <prop key="template_update_delay">10</prop>
            <prop key="locale">zh_CN</prop>
            <prop key="datetime_format">yyyy-MM-dd HH:mm:ss</prop>
            <prop key="date_format">yyyy-MM-dd</prop>
            <prop key="number_format">#.##</prop>
        </props>
    </property>
</bean>

<!-- FreeMarker視圖解析 -->
<bean id="freeMarkerViewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.freemarker.FreeMarkerView" />
    <property name="contentType" value="text/html;charset=UTF-8" />
    <property name="prefix" value="/ftl/" />
    <property name="suffix" value=".ftl" />
    <property name="exposeRequestAttributes" value="true" />
    <property name="exposeSessionAttributes" value="true" />
    <property name="exposeSpringMacroHelpers" value="true" />
    <property name="order" value="0"/>
</bean>

這裏jsp的 order 要設置比freemarker的大,否則都會解析成 jsp

以上的配置,假如邏輯視圖名稱爲 test,它會先在 /ftl/ 下找 test.ftl 模板,找到就解析成 freemarker,沒有就使用 jsp 解析器去找 jsp 文件

另外能夠配置 viewNames 屬性,對視圖名稱加以區分。(如 同時配置了 thymeleaf 和 jsp )

配置 viewNames 屬性,值可使用通配符匹配,如
<property name="viewNames" value="*.html,*.xhtml" />
<property name="viewNames" value="thymeleaf/*" />
<property name="viewNames" value="*.jsp" />

2)內容協商

ContentNegotiatingViewResolver 能根據請求文件名或Accept標頭解析視圖,經過考慮客戶端須要的內容類型來解析視圖,委託給另一個可以產生對應內容類型的視圖解析器

好比請求只是 accept-type 不一樣,如 /aa, HTTP Request Header 中的 Accept 分別是 text/jsp, text/pdf, text/xml,text/json,無Accept 請求頭

ContentNegotiatingViewResolver 能夠一個 @RequestMapping,返回多個不一樣的 View

<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
   <property name="favorParameter" value="true"/>
   <property name="favorPathExtension" value="true"/>
   <property name="mediaTypes">
         <map>
            <entry key="xml" value="application/xml"/>
            <entry key="json" value="application/json"/> 
            <entry key="xls" value="application/vnd.ms-excel"/>
         </map>
   </property>
   <property name="viewResolvers"> 
      <list>
         <ref bean="jaxb2MarshallingXmlViewResolver"></ref>
         <ref bean="jsonViewResolver"></ref>
         <ref bean="excelViewResolver"></ref>
      </list>
   </property>
</bean>

3)自定義多視圖解析器

另外,還能夠自定義多視圖解析器,根據返回視圖名稱的後綴不一樣或是參數值不一樣分別委託給其餘的視圖解析器。這裏就不介紹了

3、MVC 配置視圖解析器

MVC 配置簡化了視圖解析器的註冊,須要使用 mvc 命名模式

如下示例使用 JSP 和 Jackson 來配置內容協商視圖解析

<mvc:annotation-driven/>

<mvc:view-resolvers>
    <mvc:content-negotiation>
        <mvc:default-views>
            <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
        </mvc:default-views>
    </mvc:content-negotiation>
    <mvc:jsp/>
</mvc:view-resolvers>

如下是 FreeMarker 的示例

<mvc:annotation-driven/>

<mvc:view-resolvers>
    <mvc:content-negotiation>
        <mvc:default-views>
            <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
        </mvc:default-views>
    </mvc:content-negotiation>
    <mvc:freemarker cache="false"/>
</mvc:view-resolvers>

<mvc:freemarker-configurer>
    <mvc:template-loader-path location="/freemarker"/>
</mvc:freemarker-configurer>
相關文章
相關標籤/搜索