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>
相关文章
相关标签/搜索