最近花了点时间了解了一下Thymeleaf的简单应用,毕竟现在jsp大势已去,spring官方也在推Thymeleaf,简单的掌握一点知识,以后会用到。而且现在公司项目中已经涉及到了,虽然比较简单。
这个文章大概参考了两篇文章,一篇是:入门例子;另一篇是:表单提交。
主要涉及到的标签只有,th:text
——取值,th:object
——取对象,th:if
——条件判断,th:each
——循环取值,th:action
——form表单中的地址,th:method
——form表单中的方法。
而这些标签中又涉及到几种取值表达式:${}
这种一般在取后台数据的时候用到;*{}
这种一般在后台取到对象后,单独取对象中的每个属性值时用到;#{}
这种一般就是取静态默认属性值,如在application文件中定义好的属性值。
我们在接下来的内容中一一介绍。这里是该项目的Github地址。
pom.xml
文件中涉及的依赖如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
默认的Thymeleaf模板页面应该放在resources/templates
目录中。我们在该目录下新建一个index.html
文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>index</title>
</head>
<body>
<h1>Thymeleaf Page: Welcome to Spring Boot World!</h1>
<h1>File Location: resources/templates/index.html</h1>
</body>
</html>
然后我们新建一个HomeController
,并新建一个方法来访问该文件。
启动服务,看能否访问该页面。
接着我们通过设置thymeleaf前缀来修改文件的访问路径。
我们新建一个index.html
文件,在webapp/WEB-INF/page
目录下。
文件内容做了点小改动:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>index</title>
</head>
<body>
<h1>Thymeleaf Page: Welcome to Spring Boot World!</h1>
<h1>File Location: webapp/WEB-INF/page/index.html</h1>
</body>
</html>
然后在application文件中增加配置:
spring.thymeleaf.prefix=/WEB-INF/page/
再次访问,看看我们访问的是哪个路径下的文件:
可以看到访问路径发生了改变。
静态值的获取
我们在application文件中增加几个静态值:
student.id=1
student.name=Agumon
student.gender=male
student.age=3
student.telephone=8848
接着我们新建一个页面来获取这些静态值:
<!DOCTYPE html>
<html xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<title>显示学生信息</title>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
</head>
<h2>#{}直接可以取对应的属性值(如在application.properties中定义的相关属性)</h2>
编号:<span th:text="#{student.id}">2</span><br>
姓名:<span th:text="#{student.name}">mnnnb</span><br>
性别:<span th:text="#{student.gender}">男</span><br>
年龄:<span th:text="#{student.age}">20</span><br>
电话:<span th:text="#{student.telephone}">777777777</span><br>
</html>
同时在HomeController
中新增一个方法来访问这个页面:
然后我们先访问一下静态页面的展示,用如下方式:
然后可以看到页面内容
之后我们用接口访问,看一下页面是否发生了变化:
好像有点问题,我们需要做些配置:
package com.yubotao.thymeleaf.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
/**
* @Auther: yubt
* @Description:
* @Date: Created in 10:27 2018/9/7
* @Modified By:
*/
@Configuration
public class I18NConfig {
@Bean
public ResourceBundleMessageSource messageSource(){
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setUseCodeAsDefaultMessage(true);
messageSource.setFallbackToSystemLocale(false);
// 设置消息源,表明消息源是以application打头属性文件
messageSource.setBasename("application");
messageSource.setDefaultEncoding("UTF-8");
messageSource.setCacheSeconds(2);
return messageSource;
}
}
再访问,我们可以看到能够正确显示了
可以看到取值发生变化,我们通过#{}
取到了application
文件中定义的静态属性值。
然后我们在resources
中在创建一个application_zh_CN.properties
,因为我们之前的配置类是国际化配置,所以可以根据系统语言环境取对应的版本属性文件。
#由于系统语言环境原因,该文件会先于application.properties读取
student.id=1
student.name=郭文玲
student.gender=女
student.age=18
student.telephone=15890904568
访问看结果
接着我们来看看如何获取后台传过来的动态数据。
从后台接口获取数据
创建用户实体类User
package com.yubotao.thymeleaf.model;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.util.Date;
/**
* @Auther: yubt
* @Description:
* @Date: Created in 10:39 2018/9/7
* @Modified By:
*/
@ToString
public class User {
@Getter
@Setter
private int id;
@Getter
@Setter
private String username;
@Getter
@Setter
private String password;
@Getter
@Setter
private String telephone;
@Getter
@Setter
private Date registerTime;
@Getter
@Setter
private int popedom; // 权限 0-管理员 1-普通用户
}
这里伪造查询方法UserService
package com.yubotao.thymeleaf.service;
import com.yubotao.thymeleaf.model.User;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* @Auther: yubt
* @Description:
* @Date: Created in 10:43 2018/9/7
* @Modified By:
*/
@Service
public class UserService {
public User findOneUser() {
User user = new User();
user.setId(1);
user.setUsername("李文强");
user.setPassword("12345");
user.setTelephone("15890904567");
user.setRegisterTime(new Date());
user.setPopedom(0);
return user;
}
public List<User> findAllUsers() {
List<User> users = new ArrayList<User>();
User user = new User();
user.setId(1);
user.setUsername("李文强");
user.setPassword("12345");
user.setTelephone("15890904567");
user.setRegisterTime(new Date());
user.setPopedom(0);
users.add(user);
user = new User();
user.setId(2);
user.setUsername("张海洋");
user.setPassword("11111");
user.setTelephone("13990904567");
user.setRegisterTime(new Date());
user.setPopedom(1);
users.add(user);
user = new User();
user.setId(3);
user.setUsername("吴文燕");
user.setPassword("22222");
user.setTelephone("15890978905");
user.setRegisterTime(new Date());
user.setPopedom(1);
users.add(user);
user = new User();
user.setId(4);
user.setUsername("郑智化");
user.setPassword("33333");
user.setTelephone("15990956905");
user.setRegisterTime(new Date());
user.setPopedom(1);
users.add(user);
return users;
}
}
接着创建UserController
来访问页面
package com.yubotao.thymeleaf.controller;
import com.yubotao.thymeleaf.model.User;
import com.yubotao.thymeleaf.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
/**
* @Auther: yubt
* @Description:
* @Date: Created in 10:52 2018/9/7
* @Modified By:
*/
@Controller
@RequestMapping(path = "/user")
public class UserController {
@Autowired
UserService userService;
@RequestMapping("/showOneUser")
public String showOneUser(Model model){
User user = userService.findOneUser();
model.addAttribute("user",user);
return "showOneUser";
}
@RequestMapping("/showAllUsers")
public String showAllUsers(Model model){
List<User> userList = userService.findAllUsers();
model.addAttribute("users", userList);
return "showAllUsers";
}
}
新建页面showOneUser.html
<!DOCTYPE html>
<html xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8"/>
<title>显示用户信息</title>
</head>
<body>
<h1>两种形式:</h1>
<h3>第一种:直接通过${}来取值</h3>
编号:<span th:text="${user.id}"></span><br/>
用户名:<span th:text="${user.username}"></span><br/>
密码:<span th:text="${user.password}"></span><br/>
电话:<span th:text="${user.telephone}"></span><br/>
注册时间:<span th:text="${#dates.format(user.registerTime, 'yyyy-MM-dd hh:mm:ss')}"></span><br/>
权限:<span th:text="${user.popedom==0?'管理员':'普通用户'}"></span><br/>
<hr>
<h3>第二种:先通过${}取到对象,然后再通过*{}来取值</h3>
<div th:object="${user}">
编号:<span th:text="*{id}"></span><br/>
用户名:<span th:text="*{username}"></span><br/>
密码:<span th:text="*{password}"></span><br/>
电话:<span th:text="*{telephone}"></span><br/>
注册时间:<span th:text="*{#dates.format(registerTime, 'yyyy-MM-dd hh:mm:ss')}"></span><br/>
权限:<span th:text="*{popedom==0?'管理员':'普通用户'}"></span><br/>
</div>
</body>
</html>
访问查看结果
接着创建showAllUsers.html
<!DOCTYPE html>
<html xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8"/>
<title>显示全部用户</title>
</head>
<body>
<div class="panel-body">
<ul class="list-group">
<li class="list-group-item" th:each="user:${users}">
编号:<span th:text="${user.id}"></span><br/>
用户名:<span th:text="${user.username}"></span><br/>
密码:<span th:text="${user.password}"></span><br/>
电话:<span th:text="${user.telephone}"></span><br/>
注册时间:<span th:text="${#dates.format(user.registerTime, 'yyyy-MM-dd hh:mm:ss')}"></span><br/>
权限:<span th:text="${user.popedom==0?'管理员':'普通用户'}"></span><br/>
</li>
</ul>
</div>
<hr>
<div class="col-md-7">
<div class="panel panel-primary">
<div class="panel-heading text-center">
<span class="panel-title">全部用户信息</span>
</div>
<div class="panel-body">
<table class="table-bordered" style="width: 100%">
<tr style="height: 40px; background-color: #f7ecb5">
<th class="text-center">编号</th>
<th class="text-center">用户名</th>
<th class="text-center">密码</th>
<th class="text-center">电话</th>
<th class="text-center">注册时间</th>
<th class="text-center">权限</th>
</tr>
<tr th:each="user:${users}" class="text-center" style="height: 40px">
<td><span th:text="${user.id}"></span></td>
<td><span th:text="${user.username}"></span></td>
<td><span th:text="${user.password}"></span></td>
<td><span th:text="${user.telephone}"></span></td>
<td><span th:text="${#dates.format(user.registerTime, 'yyyy-MM-dd hh:mm:ss')}"></span></td>
<td><span th:text="${user.popedom==0?'管理员':'普通用户'}"></span></td>
</tr>
</table>
</div>
</div>
</div>
</body>
</html>
这里是两种样式,懒得改了,其实两种形式都一样,都是用了th:each
来循环遍历。
一些表达式和变量的使用等
我们新增一个接口
@RequestMapping("test")
public String test(Model model, HttpSession session, HttpServletRequest request, HttpServletResponse response){
WebContext ctx = new WebContext(request, response, request.getServletContext());
ctx.setVariable("book", "《智能时代》");
session.setAttribute("city", "醉美泸州");
return "testThymeleafObjects";
}
接着新建一个页面testThymeleafObjects.html
<!DOCTYPE html>
<html xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<title>测试thymeleaf对象</title>
<meta charset="UTF-8"/>
</head>
<body>
#ctx.#vars:<br/>
<textarea th:text="${#ctx.#vars}" rows="10" cols="150"></textarea><br/>
<!--这个#vars不好使-->
喜欢看的书:<span th:text="${#vars.book}"></span><br>
喜欢的城市:<span th:text="${#httpSession.getAttribute('city')}"></span><br>
你的国家:<span th:text="${#locale.country}+'-'+${#locale.getDisplayCountry()}"></span><br>
你的母语:<span th:text="${#locale.language}+'-'+${#locale.getDisplayLanguage()}"></span><br>
<hr>
时间:<span th:text="${#dates.format(#dates.createNow())}"></span><br>
收入:<span th:text="'¥' + ${#numbers.formatDecimal(2345.5645345, 3, 2)}"></span><br>
</body>
</html>
然后查看页面
这里有部分变量有问题,就是th:text="${#httpSession.getAttribute('city')}"
这里,我暂时就没管了。
这些应该都是Thymeleaf内置的一些变量什么的,感兴趣的可以查查,网上这种资料一堆,再不济去看官网。
条件判断
接着我们在resources
中新建application.yaml
文件
server:
port: 8080
serverHost:
inetAddressA:
ip: 127.0.0.1
length: 160
port: 2000
inetAddressB:
ip: 192.168.0.15
length: 180
port: 2000
inetAddressC:
ip: 192.168.0.16
length: 288
port: 2000
udp:
server:
host: 192.168.60.34
port: 8001
并创建一个ServerHostProperties
类来对应属性信息
package com.yubotao.thymeleaf.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @Auther: yubt
* @Description:
* @Date: Created in 19:16 2018/9/10
* @Modified By:
*/
@Component
@ConfigurationProperties("serverhost")
public class ServerHostProperties {
private InetAddress inetAddressA;
private InetAddress inetAddressB;
private InetAddress inetAddressC;
public static class InetAddress{
private String ip;
private int length;
private int port;
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
}
public InetAddress getInetAddressA() {
return inetAddressA;
}
public void setInetAddressA(InetAddress inetAddressA) {
this.inetAddressA = inetAddressA;
}
public InetAddress getInetAddressB() {
return inetAddressB;
}
public void setInetAddressB(InetAddress inetAddressB) {
this.inetAddressB = inetAddressB;
}
public InetAddress getInetAddressC() {
return inetAddressC;
}
public void setInetAddressC(InetAddress inetAddressC) {
this.inetAddressC = inetAddressC;
}
}
然后创建一个ServerHostController
写相关接口
package com.yubotao.thymeleaf.controller;
import com.yubotao.thymeleaf.properties.ServerHostProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.ArrayList;
import java.util.List;
/**
* @Auther: yubt
* @Description:
* @Date: Created in 19:18 2018/9/10
* @Modified By:
*/
@Controller
@RequestMapping(path = "/server")
public class ServerHostController {
@Autowired
private ServerHostProperties serverHostProperties;
@RequestMapping("/showServerHost")
public String serverHost(Model model){
List<ServerHostProperties.InetAddress> inetAddresses = new ArrayList<ServerHostProperties.InetAddress>();
inetAddresses.add(serverHostProperties.getInetAddressA());
inetAddresses.add(serverHostProperties.getInetAddressB());
inetAddresses.add(serverHostProperties.getInetAddressC());
model.addAttribute("inetAddresses", inetAddresses);
return "showServerHost";
}
}
最后我们写一个showServerHost.html
页面,其中涉及了if
判断
<!DOCTYPE html>
<html xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<title>显示服务器主机信息</title>
<meta charset="UTF-8"/>
</head>
<body>
<div class="panel-heading text-center">
<span class="panel-title">服务器主机信息</span>
</div>
<div class="panel-body">
<ul class="list-group">
<li class="list-group-item" th:each="inetAddress:${inetAddresses}">
<div th:if="${inetAddressStat.count==1}">
<p style="font-weight: bold">inetAddressA</p>
</div>
<div th:if="${inetAddressStat.count==2}">
<p style="font-weight: bold">inetAddressB</p>
</div>
<div th:if="${inetAddressStat.count==3}">
<p style="font-weight: bold">inetAddressC</p>
</div>
ip:<span th:text="${inetAddress.ip}"></span><br/>
length:<span th:text="${inetAddress.length}"></span><br/>
port:<span th:text="${inetAddress.port}"></span><br/>
</li>
</ul>
</div>
</body>
</html>
我们访问一下,看下效果
可以看到起效了。
这里顺便说一下inetAddressStat.count
这个变量,查了一下,它表示的是循环遍历的list中,某个对象是该list中的第几个对象,应该是一个排序功能。
表单提交
最后,我们看下最关键的表单提交,这也是前后端交互很常用的。
首先我们新建一个AnimalForm
package com.yubotao.thymeleaf.model;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
/**
* @Auther: yubt
* @Description:
* @Date: Created in 9:55 2018/9/12
* @Modified By:
*/
public class AnimalForm {
@Setter
@Getter
private long id;
@Setter
@Getter
@NotEmpty(message = "动物名:不能为空")
private String oname;
@Setter
@Getter
@Range(min = 1, message="数量:必须大于0")
@NotNull(message = "数量:不能为空")
private int ocount;
@Setter
@Getter
@Size(max = 10, message = "备注:长度不能超过10个字符")
private String memo;
}
然后新建一个ZooController
package com.yubotao.thymeleaf.controller;
import com.yubotao.thymeleaf.model.AnimalForm;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.validation.Valid;
/**
* @Auther: yubt
* @Description:
* @Date: Created in 9:59 2018/9/12
* @Modified By:
*/
@Controller
@RequestMapping(path = "/zoo")
public class ZooController {
@RequestMapping(path = "list", method = RequestMethod.GET)
public ModelAndView showZooList(){
ModelAndView mav = new ModelAndView();
mav.setViewName("zoolist");
mav.addObject("animalForm", new AnimalForm());
return mav;
}
@RequestMapping(path = "/list", params = {"save"}, method = RequestMethod.POST)
public String doAdd(Model model, @Valid AnimalForm animalForm, BindingResult result){
System.out.println("动物名: " + animalForm.getOname());
System.out.println("数量: " + animalForm.getOcount());
System.out.println("备注: " + animalForm.getMemo());
if (result.hasErrors()){
model.addAttribute("MSG", "出错啦!");
}else {
model.addAttribute("MSG", "提交成功!");
}
return "zoolist";
}
}
最后新建一个zoolist.html
页面
<!DOCTYPE html>
<html xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>Zoo List</title>
</head>
<body>
<a href=".">首页</a>->动物列表<br><br>
<div th:text="${MSG}">这里是信息提示</div>
<br>
<div th:errors="${animalForm.oname}"></div>
<div th:errors="${animalForm.ocount}"></div>
<div th:errors="${animalForm.memo}"></div>
<br>
<form id="iform" th:action="@{/zoo/list?save}" th:method="post" th:object="${animalForm}">
<table border="1">
<tr>
<th>动物名称</th>
<th>数量</th>
<th>备注</th>
<th>Action</th>
</tr>
<tr>
<td><input type="text" name="oname" value="" th:value="*{oname}"/></td>
<td><input type="text" name="ocount" value="" th:value="*{ocount}"/></td>
<td><input type="text" name="memo" value="" th:value="*{memo}"/></td>
<td><input type="submit" value="添加"/></td>
</tr>
</table>
</form>
<hr>
<table border="1">
<tr>
<th>序号</th>
<th>动物名称</th>
<th>数量</th>
<th>备注</th>
</tr>
<tr>
<td>1</td>
<td>大马猴</td>
<td>10</td>
<td>机灵古怪,俏皮活泼</td>
</tr>
<tr>
<td>2</td>
<td>大熊猫</td>
<td>80</td>
<td>体型笨重,喜欢吃竹子</td>
</tr>
<tr>
<td>3</td>
<td>澳洲羊驼</td>
<td>13</td>
<td>长相奇特,大国人俗称其草泥马</td>
</tr>
<tr>
<td>4</td>
<td>峨眉山猴</td>
<td>90</td>
<td>不怕人,有时候发贱抢游客面包吃</td>
</tr>
</table>
</body>
</html>
这页里面最关键的部分我给提取出来,就是
<form id="iform" th:action="@{/zoo/list?save}" th:method="post" th:object="${animalForm}">
<table border="1">
<tr>
<th>动物名称</th>
<th>数量</th>
<th>备注</th>
<th>Action</th>
</tr>
<tr>
<td><input type="text" name="oname" value="" th:value="*{oname}"/></td>
<td><input type="text" name="ocount" value="" th:value="*{ocount}"/></td>
<td><input type="text" name="memo" value="" th:value="*{memo}"/></td>
<td><input type="submit" value="添加"/></td>
</tr>
</table>
</form>
主要涉及了th:action
:用来设置请求接口的url;th:method
:用来设置请求方法;th:object
:这里和前面get方法传过来的Object名对应。
@RequestMapping(path = "list", method = RequestMethod.GET)
public ModelAndView showZooList(){
ModelAndView mav = new ModelAndView();
mav.setViewName("zoolist");
mav.addObject("animalForm", new AnimalForm());
return mav;
}
功能很简单,首先get方法请求到这个页面,然后我们在这个页面发送数据,之后查看后台控制台输出是否正确。
首先请求到页面
然后我们填上数据并提交
接着看后台控制台输出是否一致
至此form表单提交也就完成了。
这里面涉及了AnimalForm
中的几个注解,简单提一下:
@NotEmpty(message = "动物名:不能为空")
:这个属性值不能为空,否则会报错。报错信息在html文件中有写
<div th:errors="${animalForm.oname}"></div>
效果如下:
通过这些报错信息,大概能猜出那几个注解的作用。
@Range(min = 1, message="数量:必须大于0")
:控制大小,从1开始,所以就必须大于0@Size(max = 10, message = "备注:长度不能超过10个字符")
:规定了最大值为10,所以长度不能超。
最后是ZooController
中的post方法,里面有个params = {"save"}
,并且我们看到在form表单中的th:action="@{/zoo/list?save}"
,这里表示只对含有save
参数的url请求起作用,如果发现客户端传递来的参数里没有save
则不应答,对于这个方法,请求http://localhost:7777/zoo/list?save
或http://localhost:7777/zoo/list?save=222
都会应答。
@Valid
表示要对该form进行验证,spring框架会根据字段名称将页面传递过来的值绑定到animalForm中。
到此我的学习就暂时告一段落,如果后续需要更加深入的学习,或者有新的新的体会,应该会开一篇新的文章。