SpringInAction笔记(六)—— 渲染Web视图(下)
使用布局引擎,如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 © 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模板中不再包含这部分内容。这使得它们能够跨页面重用,这些元素的维护也得以简化。
运行结果:
多年来,在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>
运行结果:
上一篇: 在WPF中使用字体图标
下一篇: 在WPF中使用字体图标