Spring Mvc 视图解析html
在 Spring Mvc 中,咱们本身编写的控制器方法(Controller) 并无直接去渲染结果,使用 response 去输出到浏览器。方法返回的是 ModelAndView,甚至只是一个 String 类型的视图名,那 Spring Mvc 是怎么把模型数据填充到视图的呢?若是控制器能经过逻辑视图名来了解视图的话,那Spring Mvc 如何肯定使用哪个视图实现来渲染模型呢?java
DispatcherServlet
web
HandlerMapping
spring
HandlerAdapter
json
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
获得最终的视图对象 View
app
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; }
下面是 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 标头解析视图,经过考虑客户端须要的内容类型来解析视图,委托给另一个可以产生对应内容类型的视图解析器 |
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); } ... }
有一些视图解析器(如 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"; }
添加 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>
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 引擎,用来解析模板,并基于这些模板渲染结果。
当咱们同时使用多种视图解析的时候,该如何解析呢?
这里要分状况讨论了
能够声明多个解析器,并在必要时经过设置 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" />
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>
另外,还能够自定义多视图解析器,根据返回视图名称的后缀不一样或是参数值不一样分别委托给其余的视图解析器。这里就不介绍了
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>