官方文档https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html
thymeleaf是个模板引擎,在springboot中用来取代jsp实现前端页面的开发与后台的信息传送。
thymeleaf常用命名空间:
<!DOCTYPE html>
<html lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head></head>
<body></body>
</html>
只修改了前端页面,不需要重启tomcat,只需要ctrl+f9就行了
后台向前台传值,只要在处理请求的方法里声明Model,然后把从service层拿来的数据放到model里就行了,看下例:
@RequestMapping("/employee")
public String toForm(Model model){
//我在自己做尝试的时候为了方便直接ctronller调dao了
Collection<Employee> employees = employeeDao.getAllEmployees();
//利用addAttribute()方法来放拿到的值,前台通过model.addAttribute("employees",employees)中的"employees"来拿到employees
model.addAttribute("employees",employees);
return "form";
}
利用model的addAttribute()方法来放拿到的值,前台通过model.addAttribute(“employees”,employees)中的"employees"来拿到employees这个集合。当然除了能传collection,传其他任何值都可以。
后台把数据放好,前台就可以来取了,thymeleaf取值用${},看下例,去除了之前放在model里的employees并遍历:
<!--通过${employees}取后台放进model里的值-->
<tr th:each="employee:${employees}">
<td th:text="${employee.getId()}"></td>
<td th:text="${employee.getLastName()}"></td>
<td th:text="${employee.getEmail()}"></td>
<td th:text="${employee.getGender()}==0?'女':'男'"></td>
<td th:text="${employee.getDepartment().getDepartmentName()}"></td>
<td th:text="${#dates.format(employee.getBirth(),'yyyy-MM-dd')}"></td>
<td>
<a class="btn btn-sm btn-primary" th:href="@{/updateEmployee/}+${employee.getId()}" >修改</a>
<a class="btn btn-sm btn-danger" th:href="@{/deleteEmployee/}+${employee.getId()}">删除</a>
</td>
</tr>
th可以接管标签的任何属性,th:each="employee:${employees}"是thymeleaf具有的表达式,意思是for(Employee employee : employees){},这会遍历出employees中的每个employee,然后给后面的td标签使用。td标签的text属性被thymeleaf接管,会显示从employee中取出的值。employee是个对象,所有可以调用对象的getter来得到想要的值。
thymeleaf向后台传值,在表单中,只要写上input标签的name属性,后台就可以根据这个name属性来拿到值了。例如下面的实现登录用户名与密码提交的例子:
<form class="form-signin" th:action="@{/user/login}"><!--这个表单会提交/user/login请求-->
<h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
<!--加了name属性,后台就能根据name拿到值了-->
<input type="email" name="username" id="inputEmail" class="form-control" placeholder="Email address">
<input type="password" name="password" id="inputPassword" class="form-control" placeholder="Password">
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
</form>
当点击提交按钮时,这个表单会提交/user/login请求,被对应的请求映射处理,同时form的内容也随请求被传给后台代码。
@RequestMapping("/user/login")
public String signInTest(
@RequestParam("username")String username,//@RequestParam("")可以根据标签的name拿到前台的值
@RequestParam("password")String password,
Model model,
HttpSession session
){
//拿到参数后就来写逻辑了,判断用户名密码是否正确等等,这里先简化了写
//如果输入的用户名不为空且密码为123456则让他登录成功
if(!StringUtils.isEmpty(username) && password.equals("123456")){
//登陆成功,重定向到dashboard页
session.setAttribute("logInUser",username);//登陆成功后设置session,方便拦截功能的实现
return "redirect:/dashboard.html";//重定向到/dashboard.html
} else {
//登录失败,转发到siginin
return "signin";
}
}
在参数列表中的参数前面写上@RequestParam(“username”)表示这个参数是对应前台传来的name属性为username的input标签的值。实际上,不写这个注解,将参数名定义为和name属性相同的名字也可以自动的匹配上值。
前台向后台传值还有更方便的用法,当表单可以对应一个对象时,直接将处理请求的方法的参数设置为一个对象,表单的值就会自动地与对象的属性一一对应,前提是表单中input标签的name属性的值与类中属性的变量名一摸一样。看下例:
<h2>添加员工</h2>
<form th:action="@{/addEmployee}" method="post">
<div class="form-group">
<label>LastName</label>
<input type="text" class="form-control" name="lastName">
</div>
<div class="form-group">
<label>Email</label>
<input type="email" class="form-control" name="email">
</div>
<div class="form-group">
<label>Gender</label><br>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="1">
<label class="form-check-label">男</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="0">
<label class="form-check-label">女</label>
</div>
</div>
<div class="form-group">
<label>department</label>
<select class="form-control" name="department">
<option th:each="department:${departments}" th:text="${department.getDepartmentName()}" th:value="${department.getId()}"></option>
</select>
</div>
<div class="form-group">
<label>Birth</label>
<input type="text" class="form-control" name="birth">
</div>
<button type="submit" class="btn btn-primary">添加</button>
</form>
上面的表单实现了输入信息后添加一个员工,员工的实体类:
import java.util.Date;
public class Employee {
private Integer id;
private String lastName;
private String email;
private Integer gender; //0:man,1:woman
private Department department;
private Date birth;
//constructor&getter&setter
}
可以看出表单中input标签的name属性的值与类中属性的变量名是一一对应的,这样就能实现自动传值了。处理请求的代码如下:
@PostMapping("/addEmployee")//addEmployee页提交表单,post请求
public String addEmployee(Employee employee){//前台传来的参数自动地被封装成一个对象,前提是前台的那么属性要与对象中的属性名一致
employeeDao.addAEmployee(employee);
return "redirect:/employee";
}
thymeleaf中的@{}是用来放链接、地址的,页面路由和html页面访问css、js、image等其他静态资源都可以被thymeleaf托管。
页面路由
页面路由由后端代码来处理,前台只需要发送http请求,后端请求映射方法就会处理并实现页面路由(重定向、转发)。看下例:
<form class="form-signin" th:action="@{/user/login}"><!--这个表单会提交/user/login请求-->
<h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
<!--加了name属性,后台就能根据name拿到值了-->
<input type="email" name="username" id="inputEmail" class="form-control" placeholder="Email address">
<input type="password" name="password" id="inputPassword" class="form-control" placeholder="Password">
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
</form>
上面的html片段是实现登录功能的页面,我们可以注意到form标签中的action属性被thymeleaf托管了,只要点击按钮,这个表单就会携带参数向发送 /user/login http请求,然后被后台对应的代码处理:
@RequestMapping("/user/login")
public String signInTest(
@RequestParam("username")String username,//@RequestParam("")可以根据标签的name拿到前台的值
@RequestParam("password")String password,
Model model,
HttpSession session
){
//拿到参数后就来写逻辑了,判断用户名密码是否正确等等,这里先简化了写
//如果输入的用户名不为空且密码为123456则让他登录成功
if(!StringUtils.isEmpty(username) && password.equals("123456")){
//登陆成功,重定向到dashboard页
session.setAttribute("logInUser",username);//登陆成功后设置session,方便拦截功能的实现
return "redirect:/dashboard.html";//重定向到/dashboard.html
} else {
//登录失败,转发到siginin
return "signin";
}
}
从上面的java代码可见页面路由是后端处理的。页面跳转有重定向和转发两种,重定向是 return “redirect:/xxxxl”; 转发是 return “xxxx”; 二者有着较大的区别(这里在springmvc中就应该搞清楚)。
html页面访问其他静态资源
在html中通常会导入其他静态资源,这些静态资源可以一股脑的放在static目录下然后再html中直接取,但在转发时会造成访问不到的问题。也可以托管给thymeleaf,然后再访问,为了整理方便,我再static目录下新建了/css、/js、/img三个文件夹用来存放不同的静态资源。
<link th:href="@{/css/bootstraps.min.css}" rel="stylesheet">
上例中,/代表当前项目目录,css放在static目录下,正常来说应该是/static/css,但static不用写,所以直接用/css/**来访问这个css目录下的css文件了。同理访问js与图片:
<script th:src="@{/js/feather.min.js}"></script>
<img class="mb-4" th:src="@{/img/bootstrap-solid.svg}" width="72" height="72">
要注意的是,再设置拦截器的时候要注意放行这些静态资源的地址。
restful风格可以使得路径变得更加的简洁,也可以通过不同的请求方式映射到同一路径的不同处理方法上。
例如,下面的代码,不使用restful风格:
@RequestMapping("/add")
@ResponseBody
public String toAdd(int a,int b){
int c = a + b;
return "c";
}
请求的路径可能为下,路径上会显示出传的参数:
localhost:8080/add?a=1&b=2
这样不太好看,且会暴露后台代码(变量名暴露出来了)
使用restful风格如下(看注释)
@RequestMapping("/add/{a}/{b}")//{a}这样来传参
@ResponseBody
public String toAddRestful(@PathVariable int a,@PathVariable int b){
//在参数列表中使用@PathVariable声明参数
int c = a + b;
return "c";
}
请求路径就可以变为下面了:
localhost:8080/add/1/2
变得优雅许多
使用restful风格,同一请求路径可以映射到不同的处理方法上,通过请求方式的不同来实现。
比如下面的例子:
@GetMapping("/add")
@ResponseBody
public String toAddRestful(){
return "c";
}
@PostMapping("/add")
@ResponseBody
public String toAddRestful(){
return "d";
}
========================================
<a th:href="@{/add}" method="get"></a>
上面的a标签会映射到getmapping
========================================
<a th:href="@{/add}" method="post"></a>
上面的a标签会映射到postmapping
resultful风格实际使用:
<a class="btn btn-sm btn-primary" th:href="@{/updateEmployee/}+${employee.getId()}" >修改</a>
这里href属性直接通过拼接实现restful传参,${employee.getId()}是得到当前employee对象的id。处理的后台代码:
@GetMapping("/updateEmployee/{id}")//restful风格
public String toUpdatePage(@PathVariable("id")Integer id,Model model){
Employee employee = employeeDao.selectAEmployeeById(id);
model.addAttribute("employee",employee);
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("departments",departments);
return "updateEmployee";
}