这篇文章,我们将会讲解关于使用Thymeleaf渲染Web网页。这个系列大概还会有一篇文章讲解Spring Security,接来来我就会开始使用Spring-boot+Angular框架来搭建Web应用。
Jsp虽然在表单绑定方面使用很简单,灵活易懂,但是Jsp的网页的格式都不太友好,Jsp文件看上去像是XML和HTML,但是实际上并不是。并且,JSP和Servlet是紧密耦合的,所以我们只能在基于servlet的Web应用中使用JSP。
我们使用之前Spitter项目中的最简单的home.jsp文件,来展示Thymeleaf:
1.配置Thymeleaf:
如果我们想在Spring中使用Thymeleaf,那么我们需要配置以下内容:
a.ThymeleafViewResolver:Thymeleaf视图解释器。
b.SpringTemplateEngine:处理模板渲染结果
c.TemplateResolver:加载Thymeleaf模板
我们依然使用Java来配置Thymeleaf:
package spittr.web;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
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.thymeleaf.spring4.SpringTemplateEngine;
import org.thymeleaf.spring4.view.ThymeleafViewResolver;
import org.thymeleaf.templateresolver.ServletContextTemplateResolver;
import org.thymeleaf.templateresolver.TemplateResolver;
@Configuration
@EnableWebMvc
@ComponentScan("spittr.web")
public class WebConfig extends WebMvcConfigurerAdapter {
@Bean
public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine);
return viewResolver;
}
@Bean
public SpringTemplateEngine templateEngine(TemplateResolver templateResolver) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
return templateEngine;
}
@Bean
public TemplateResolver templateResolver() {
TemplateResolver templateResolver = new ServletContextTemplateResolver();
templateResolver.setPrefix("/WEB-INF/views/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode("HTML5");
return templateResolver;
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
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输出。
2.Thymeleaf模板:
<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>
我们在href的前面添加了th:,这样,我们的href标签就可以使用thymeleaf的表达式,计算动态的值,例如本文中的@{/resources/style.css}。JSP最好的功能就是很方便的进行表单的绑定。那么我们的thymeleaf可以很好的实现这个功能么?答案是肯定的。
我们来看一下registration.jsp中的FirstName
<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域。这可能与你的预期有点差别。在Thymeleaf模板中,我们在很多情况下所使用的属性都对应于标准的HTML属性,因此貌似使用th:value属性来设置<input>标签的value属性才是合理的。
其实不然,因为我们是在将这个输入域绑定到后端对象的firstName属性上,因此使用th:field属性引用firstName域。通过使用th:field,我们将value属性设置为firstName的值,同时还会将name属性设置为firstName
<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>
你可以注意到上面程序使用了相同的Thymeleaf属性和“*{}”表达式,为所有的表单域绑定后端对象。这其实重复了我们在First Name域中所做的事情。但是,需要注意我们在表单的顶部了也使用了Thymeleaf,它会用来渲染所有的错误。<div>元素使用th:if属性来检查是否有校验错误。如果有的话,会渲染<div>,否则的话,它将不会渲染。
在<div>中,会使用一个无顺序的列表来展现每项错误。<li>标签上的th:each属性将会通知Thymeleaf为每项错误都渲染一个<li>,在每次迭代中会将当前错误设置到一个名为err的变量中。
<li>标签还有一个th:text属性。这个命令会通知Thymeleaf计算某一个表达式(在本例中,也就是err变量)并将它的值渲染为<li>标签的内容体。实际上的效果就是每项错误对应一个<li>元素,并展现错误的文本。
你可能会想知道“${}”和“*{}”括起来的表达式到底有什么区别。“${}”表达式(如${spitter})是变量表达式(variable expression)。一般来讲,它们会是对象图导航语言(Object-Graph Navigation Language,OGNL)表达式(http://commons.apache.org/proper/commons-ognl/)。但在使用Spring的时候,它们是SpEL表达式。在${spitter}这个例子中,它会解析为key为spitter的model属性。
而对于“*{}”表达式,它们是选择表达式(selection expression)。变量表达式是基于整个SpEL上下文计算的,而选择表达式是基于某一个选中对象计算的。在本例的表单中,选中对象就是<form>标签中th:object属性所设置的对象:模型中的Spitter对象。因此,“*{firstName}”表达式就会计算为Spitter对象的firstName属性
P.S.文章不妥之处还望指正