欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

SpringInAction笔记(六)—— 渲染Web视图(下)

程序员文章站 2024-02-16 10:50:16
...
3、使用Apache Tiles视图定义布局
       使用布局引擎,如Apache Tiles,定义适用于所有页面的通用页面布局。Spring MVC以视图解析器的形式为Apache Tiles提供了支持,这个视图解析器能够将逻辑视图名解析为Tile定义。
       需导入Tiles相关的jar包:
  tiles-api-3.0.8.jar
  tiles-core-3.0.8.jar
  tiles-jsp-3.0.8.jar
  tiles-servlet-3.0.8.jar
  tiles-template-3.0.8.jar
       slf4j-api-1.7.25  (java.lang.ClassNotFoundException: org.slf4j.LoggerFactory)
       commons-digester-2.1 (java.lang.ClassNotFoundException: org.apache.commons.digester.Rule)
       commons-beanutils-1.9.3 (ClassNotFoundException: org.apache.commons.beanutils.MethodUtils)
       tiles-request-api-1.0.7.(java.lang.NoClassDefFoundError: org/apache/tiles/request/render/Renderer)
       tiles-request-servlet-1.0.7.(ClassNotFoundException: org.apache.tiles.request.servlet.ServletApplicationContext)
       tiles-request-jsp-1.0.7 (ClassNotFoundException: org.apache.tiles.request.jsp.autotag.JspAutotagRuntime)
       tiles-autotag-core-runtime-1.2 (ClassNotFoundException: org.apache.tiles.autotag.core.runtime.AutotagRuntime)

3.1 配置Tiles视图解析器
       为了在Spring中使用Tiles,需要配置几个bean。我们需要一个TilesConfigurer bean,它会负责定位和加载Tile定义并协调生成Tiles。除此之外,还需要TilesViewResolver bean将逻辑视图名称解析为Tile定义。
       这两个组件又有两种形式:针对Apache Tiles 2和Apache Tiles 3分别都有这么两个组件。这两组Tiles组件之间最为明显的区别在于包名。针对Apache Tiles 2的TilesConfigurer/TilesViewResolver位于org.springframework.web.servlet.view.tiles2包中,而针对Tiles 3的组件位于org.springframework.web.servlet.view.tiles3包中。对于该例子来讲,我们使用的是Tiles 3。

WebConfig.java:

package spittr.config;

import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.tiles3.TilesConfigurer;
import org.springframework.web.servlet.view.tiles3.TilesViewResolver;

@Configuration
@EnableWebMvc  //启用Spring Mvc
@ComponentScan("spittr.web")
public class WebConfig extends WebMvcConfigurerAdapter {

	/**
	 * 配置Tiles视图解析器
	 * @return
	 */
	@Bean
	public ViewResolver viewResolver() {	
		return new TilesViewResolver();
	}
	  
	// Tiles 
	@Bean	  
	public TilesConfigurer tilesConfigurer() {
	    TilesConfigurer tiles = new TilesConfigurer();
	    tiles.setDefinitions(new String[] {
	        "/WEB-INF/layout/tiles.xml",
	    });
	    //启用刷新功能
	    tiles.setCheckRefresh(true);
	    return tiles;	  
	}

	/**
	 * 配置静态资源的处理
	 * 此配置要求DispatcherServlet将对静态资源的请求转发到Servlet容器中默认的Servlet上,
	 * 而非使用DispatcherServlet本身来处理此类请求
	 */
	@Override
	public void configureDefaultServletHandling(
			DefaultServletHandlerConfigurer configurer) {
		// TODO Auto-generated method stub
		//super.configureDefaultServletHandling(configurer);
		configurer.enable();
	}	
	  
	@Bean	  
	public MessageSource messageSource() {
	    ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
	    messageSource.setBasename("/resources/messages");
	    messageSource.setCacheSeconds(10);
	    return messageSource;	  
	}
	
}

当配置TilesConfigurer的时候,所要设置的最重要的属性就是definitions。这个属性接受一个String类型的数组,其中每个条目都指定一个Tile定义的XML文件。对于Spittr应用来讲,我们让它在“/WEB-INF/layout/”目录下查找tiles.xml。

       还可以指定多个Tile定义文件,甚至能够在路径位置上使用通配符,当然在上例中我们没有使用该功能。例如,我们要求TilesConfigurer加载“/WEB-INF/”目录下的所有名字为tiles.xml的文件,那么可以按照如下的方式设置definitions属性:

	    tiles.setDefinitions(new String[] {
	        "/WEB-INF/**/tiles.xml",
	    });

       接下来配置TilesViewResolver,可以看到,这是一个很基本的bean定义,没有什么要设置的属性:

	@Bean
	public ViewResolver viewResolver() {	
		return new TilesViewResolver();
	}

定义Tiles
        Apache Tiles提供了一个文档类型定义(document type definition,DTD),用来在XML文件中指定Tile的定义。每个定义中需要包含一个<definition>元素,这个元素会有一个或多个<putattribute>元素。例如,如下的XML文档为Spittr应用定义了几个Tile。

        程序清单6.2 WEB-INF/layout/tiles.xml:

<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE tiles-definitions PUBLIC
       "-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN"
       "http://tiles.apache.org/dtds/tiles-config_3_0.dtd">
<tiles-definitions>

  <!-- 定义base Tile -->
  <definition name="base" template="/WEB-INF/layout/page.jsp">
    <put-attribute name="header" value="/WEB-INF/layout/header.jsp" />
    <put-attribute name="footer" value="/WEB-INF/layout/footer.jsp" />
  </definition>

  <!-- 扩展base -->
  <definition name="home" extends="base">
    <put-attribute name="body" value="/WEB-INF/views/home.jsp" />
  </definition>

  <definition name="registerForm" extends="base">
    <put-attribute name="body" value="/WEB-INF/views/registerForm.jsp" />
  </definition>

  <definition name="profile" extends="base">
    <put-attribute name="body" value="/WEB-INF/views/profile.jsp" />
  </definition>

  <definition name="spittles" extends="base">
    <put-attribute name="body" value="/WEB-INF/views/spittles.jsp" />
  </definition>

  <definition name="spittle" extends="base">
    <put-attribute name="body" value="/WEB-INF/views/spittle.jsp" />
  </definition>

</tiles-definitions>
       每个<definition>元素都定义了一个Tile,它最终引用的是一个JSP模板。在名为base的Tile中,模板引用的是“/WEBINF/
layout/page.jsp”。某个Tile可能还会引用其他的JSP模板,使这些JSP模板嵌入到主模板中。对于base Tile来讲,它引用的是一个头部JSP模板和一个底部JSP模板。

       程序清单6.3 主布局模板page.jsp:引用其他模板来创建视图

<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="t" %>
<%@ page session="false" %>
<html>
  <head>
    <title>Spittr</title>
    <link rel="stylesheet" 
          type="text/css" 
          href="<s:url value="/resources/style.css" />" >
  </head>
  <body>
    <!-- 插入头部 -->
    <div id="header">
      <t:insertAttribute name="header" />
    </div>
    <!-- 插入主题内容 -->
    <div id="content">
      <t:insertAttribute name="body" />
    </div>
    <!-- 插入底部 -->
    <div id="footer">
      <t:insertAttribute name="footer" />
    </div>
  </body>
</html>

使用Tile标签库中的<t:insert Attribute> JSP标签来插入其他的模板,在这里,用它来插入名为header、body和footer的模板.

        在base Tile定义中,header和footer属性分别被设置为引用“/WEBINF/layout/ header. jsp”和“/WEB-INF/layout/footer.jsp”。但是body属性呢?它是在哪里设置的呢?
       base Tile不会期望单独使用,它会作为基础定义,供其他的Tile定义扩展。在程序清单6.2的其余内容中,可以看到其他的Tile定义都是扩展自base Tile。它意味着它们会继承其header和footer属性的设置(当然,Tile定义中也可以覆盖掉这些性),但是每一个都设置了body属性,用来指定每个Tile特有的JSP模板。

       关注home Tile,它扩展了base。因为它扩展了base,因此它会继承base中的模板和所有的属性。尽管home Tile定义相对来说很简单,但是它实际上包含了如下的定义:

  <definition name="home" extends="base">
    <put-attribute name="header" value="/WEB-INF/layout/header.jsp" />
    <put-attribute name="footer" value="/WEB-INF/layout/footer.jsp" />
    <put-attribute name="body" value="/WEB-INF/views/home.jsp" />
  </definition>

属性所引用的每个模板是很简单的,如下是header.jsp模板:

<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
<a href="<s:url value="/" />"><img 
    src="<s:url value="/resources" />/images/spitter_logo_50.png" 
    border="0"/></a>

footer.jsp模板更为简单:

Copyright &copy; Craig Walls

每个扩展自base的Tile都定义了自己的主体区模板,所以每个都会与其他的有所区别。

home.jsp:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<h1>Welcome to Spitter</h1>

<a href="<c:url value="/spittles" />">Spittles</a> | 
<a href="<c:url value="/spitter/register" />">Register</a>

这里的关键点在于通用的元素放到了page.jsp、header.jsp以及footer.jsp中,其他的Tile模板中不再包含这部分内容。这使得它们能够跨页面重用,这些元素的维护也得以简化。

运行结果:
SpringInAction笔记(六)—— 渲染Web视图(下)
 
SpringInAction笔记(六)—— 渲染Web视图(下)
SpringInAction笔记(六)—— 渲染Web视图(下)

 
4、使用Thymeleaf
        多年来,在Java应用中,有多个项目试图挑战JSP在视图领域的统治 性地位。最新的挑战者是Thymeleaf,它展现了一些切实的承诺,是 一项很令人兴奋的可选方案。Thymeleaf模板是原生的,不依赖于标签库。它能在接受原始HTML的地方进行编辑和渲染。因为它没有与 Servlet规范耦合,因此Thymeleaf模板能够进入JSP所无法涉足的领域。
4.1 配置Thymeleaf视图解析器
        为了要在Spring中使用Thymeleaf,我们需要配置三个启用Thymeleaf与 Spring集成的bean:
        (1)ThymeleafViewResolver:将逻辑视图名称解析为Thymeleaf 模板视图;
        (2)SpringTemplateEngine:处理模板并渲染结果;
        (3)TemplateResolver:加载Thymeleaf模板。
需要引入以下jar包:
    thymeleaf-3.0.9
    thymeleaf-spring4-3.0.9
    attoparser-2.0.4
    unbescape-1.1.5

清单 WebConfig.java:

package spittr.config;

import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.thymeleaf.spring4.SpringTemplateEngine;
import org.thymeleaf.spring4.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring4.view.ThymeleafViewResolver;
import org.thymeleaf.templateresolver.ITemplateResolver;

@Configuration
@EnableWebMvc  //启用Spring Mvc
@ComponentScan("spittr.web")
public class WebConfig extends WebMvcConfigurerAdapter {

	/**
	 * 配置Thymeleaf视图解析器
	 * @return
	 */
	@Bean
	public ThymeleafViewResolver viewResolver(SpringTemplateEngine templateEngine) {	
	    ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
	    viewResolver.setTemplateEngine(templateEngine);
	    return viewResolver;
	}
	
	/**
	 * 模板引擎,处理模板并渲染结果
	 * @param templateResolver
	 * @return
	 */
	@Bean	  
	public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
	    SpringTemplateEngine templateEngine = new SpringTemplateEngine();
	    templateEngine.setTemplateResolver(templateResolver);
	    return templateEngine;  
	}
	
	/**
	 * 模板解析器
	 * @return
	 */
	@Bean  
	public ITemplateResolver templateResolver() {
		// SpringResourceTemplateResolver自动与Spring自己集成, 资源解决基础设施, 强烈推荐。
		SpringResourceTemplateResolver  templateResolver = new SpringResourceTemplateResolver ();
	    templateResolver.setPrefix("/WEB-INF/views/");
	    templateResolver.setSuffix(".html");
	    //要解析的模板会渲染成HTML5输出
	    templateResolver.setTemplateMode("HTML5");
	    // 默认情况下, 模板缓存为true。如果您想要设置为false。模板在修改时自动更新。
	    templateResolver.setCacheable(true);
	    return templateResolver;	  
	}

	/**
	 * 配置静态资源的处理
	 * 此配置要求DispatcherServlet将对静态资源的请求转发到Servlet容器中默认的Servlet上,
	 * 而非使用DispatcherServlet本身来处理此类请求
	 */
	@Override
	public void configureDefaultServletHandling(
			DefaultServletHandlerConfigurer configurer) {
		// TODO Auto-generated method stub
		//super.configureDefaultServletHandling(configurer);
		configurer.enable();
	}	
	  
	@Bean	  
	public MessageSource messageSource() {
	    ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
	    messageSource.setBasename("/resources/messages");
	    messageSource.setCacheSeconds(10);
	    return messageSource;	  
	}
	
}

ThymeleafViewResolver是Spring MVC中ViewResolver的一个实现类。像其他的视图解析器一样,它会接受一个逻辑视图名称,并 将其解析为视图。不过在该场景下,视图会是一个Thymeleaf模板。       

   需要注意的是ThymeleafViewResolver bean中注入了一个对 SpringTemplate Engine bean的引用。SpringTemplateEngine会在Spring中启用Thymeleaf引擎,用来解析模板,并基于这些模板渲染结果。可以看到,我们为其注入了一个TemplateResolver bean的引用。      

      TemplateResolver会最终定位和查找模板。与之前配 置InternalResource-ViewResolver类似,它使用了prefix和suffix属性。前缀和后缀将会与逻辑视图名组合使用,进而定位Thymeleaf引擎。它的templateMode属性被设置成了HTML 5,这表明我们预期要解析的模板会渲染成HTML 5输出。


4.2 定义Thymeleaf模板        

Thymeleaf在很大程度上就是HTML文件,与JSP不同,它没有什么特 殊的标签或标签库。Thymeleaf之所以能够发挥作用,是因为它通过 自定义的命名空间,为标准的HTML标签集合添加Thymeleaf属性。如下的程序清单展现了home.html,也就是使用Thymeleaf命名空间的首页模板。

清单 home.html:      

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
  <head>
    <title>Spitter</title>
    <link rel="stylesheet" 
          type="text/css" 
          th:href="{/resources/style.css}"></link>
  </head>
  <body>
    <div id="header" th:include="page :: header"></div>
  
    <div id="content">
      <h1>Welcome to Spitter</h1>
  
      <a th:href="@{/spittles}">Spittles</a> | 
      <a th:href="@{/spitter/register}">Register</a>
      
      <br/>
      
      View: <span th:text="${view}">unknown</span>
    </div>
    <div id="footer" th:include="page :: copy"></div>
  </body>
</html>

首页模板相对来讲很简单,只使用了th:href属性。这个属性与对 应的原生HTML属性很类似,也就是href属性,并且可以按照相同的方式来使用。th:href属性的特殊之处在于它的值中可以包含Thymeleaf表达式,用来计算动态的值。它会渲染成一个标准的href 属性,其中会包含在渲染时动态创建得到的值。

       “@{}”表达式用来计算相对于 URL的路径(就像在JSP页面中,我们可能会使用的JSTL <c:url>标 签或Spring<s:url>标签类似)。


借助Thymeleaf实现表单绑定
       表单绑定是Spring MVC的一项重要特性。它能够将表单提交的数据填充到命令对象中,并将其传递给控制器,而在展现表单的时候,表单 中也会填充命令对象中的值。如果没有表单绑定功能的话,我们需要确保HTML表单域要映射后端命令对象中的属性,并且在校验失败后 展现表单的时候,还要负责确保输入域中值要设置为命令对象的属 性。

       作为阐述的样例,请参考如下的Thymeleaf模板片段,它会渲染First Name输入域:

        <label th:class="${#fields.hasErrors('firstName')}? 'error'">First Name</label>: 
          <input type="text" th:field="*{firstName}"  
                 th:class="${#fields.hasErrors('firstName')}? 'error'" /><br/>
       在这里,我们不再使用Spring JSP标签中的cssClassName属性,而是在标准的HTML标签上使用th:class属性。th:class属性会渲染为一个class属性,它的值是根据给定的表达式计算得到的。在上面的这两个th:class属性中,它会直接检查firstName域有没有校验错误。如果有的话,class属性在渲染时的值为error。如果这 个域没有错误的话,将不会渲染class属性。
       <input>标签使用了th:field属性,用来引用后端对象的 firstName域。

清单  /spittr/WebRoot/WEB-INF/views/registerForm.html:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
  <head>
    <title>Spitter</title>
    <link rel="stylesheet" type="text/css" 
          th:href="{/resources/style.css}"></link>
  </head>
  <body>
    <div id="header" th:include="page :: header"></div>

    <div id="content">
      <h1>Register</h1>
  
      <form method="POST" th:object="${spitter}">
        <div class="errors" th:if="${#fields.hasErrors('*')}">
          <ul>
            <li th:each="err : ${#fields.errors('*')}" 
                th:text="${err}">Input is incorrect</li>
          </ul>
        </div>
        <label th:class="${#fields.hasErrors('firstName')}? 'error'">First Name</label>: 
          <input type="text" th:field="*{firstName}"  
                 th:class="${#fields.hasErrors('firstName')}? 'error'" /><br/>
  
        <label th:class="${#fields.hasErrors('lastName')}? 'error'">Last Name</label>: 
          <input type="text" th:field="*{lastName}"
                 th:class="${#fields.hasErrors('lastName')}? 'error'" /><br/>
  
        <label th:class="${#fields.hasErrors('email')}? 'error'">Email</label>: 
          <input type="text" th:field="*{email}"
                 th:class="${#fields.hasErrors('email')}? 'error'" /><br/>
  
        <label th:class="${#fields.hasErrors('username')}? 'error'">Username</label>: 
          <input type="text" th:field="*{username}"
                 th:class="${#fields.hasErrors('username')}? 'error'" /><br/>
  
        <label th:class="${#fields.hasErrors('password')}? 'error'">Password</label>: 
          <input type="password" th:field="*{password}"  
                 th:class="${#fields.hasErrors('password')}? 'error'" /><br/>
        <input type="submit" value="Register" />
      
      
      </form>
    </div>
    <div id="footer" th:include="page :: copy"></div>
  </body>
</html>

运行结果:
SpringInAction笔记(六)—— 渲染Web视图(下)
SpringInAction笔记(六)—— 渲染Web视图(下)