目录
thymeleaf初体验:
使用模板引擎的步骤:
1.引入thymeleaf页面模板引擎:
<!--thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2.在每个页面都要引入thymeleaf命名空间,如下:
<html xmlns:th="http://www.thymeleaf.org">
链接的使用:
代码演示:
第一步:导入spring-boot-start-thymeleaf的场景依赖;
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
success页面准备:
注意命名空间的引入:
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title >Title</title>
</head>
<body>
<h1 th:text="${msg}">这是页面的默认值</h1><br>
<!--th:text="${msg}"从后台取值-->
</body>
</html>
controller接收请求:
package com.fan.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class ViewTestController {
@RequestMapping("/toPage")
public String toPage(Model model){
System.out.println("测试thy");
//model中的数据是放在请求域中request.setAttribute("a","abc");
model.addAttribute("msg","你好,模板引擎,thymeleaf");
return "success";
}
}
然后页面发送请求进行测试:
http://localhost:8080/toPage
结果:
springboot整合thymeleaf页面:
spring-boot-configuration-processor包是一个让yml文件自定义的配置能提示的一个工具。
构建后台管理系统的模板(登录模块):
登录页的跳转:
检查登录页的css等静态资源的引用:
解决重复提交页面表单的处理:
两个方法:
controller代码演示:
package com.fan.controller;
import com.fan.pojo.LoginUser;
import com.fan.service.LoginUserService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.annotation.Resource;
import javax.servlet.http.HttpSession;
@Controller
public class LoginController {
@Resource
private LoginUserService loginUserService;
@GetMapping(value = {"/login","/"})
public String tologin(){
return "login";
}
//****核心是讲一个登录分成了两个,一个是重定向到主页,一个是接收重定向请求,转发到主页
@PostMapping(value = "/login")
public String login(LoginUser loginUser, HttpSession session, Model model, RedirectAttributes attributes){
LoginUser loginUser1 = loginUserService.getLoginUserByNameAndPassword(loginUser);
System.out.println("前端传一个user对象,从后台查出的loginUser:"+loginUser1);
if(loginUser1 != null ){
session.setAttribute("loginUser",loginUser1);
model.addAttribute("msg","登录成功");
//因为转发地址栏不会变,所以登录成功后,我们要让地址栏发生改变,这里选择重定向跳转页面
return "redirect:/main.html";//发送一个main的get请求
}else{
model.addAttribute("msg","用户名或者密码错误");
return "login";//要知道重定向是不能带model,所以这里用转发
}
}
@GetMapping("/main.html")//类似于我们请求一个js,或者html页面资源
public String mainPage(HttpSession session,Model model){
//为了不让用户直接登录此页面,我们需要验证session
Object loginUser = session.getAttribute("loginUser");
if(loginUser != null){
return "main";
}else{
//踢回到登录页
model.addAttribute("msg","请重新登录");
return "login";
}
}
}
测试:当我们直接访问main页面的时候:
替换右上角登录的用户名和头像:
我们原先的做法:在一个标签内写,然后后台取到的值会覆盖掉两个标签之间的内容。
现在我们需要一个行内写法:[[${}]]
代码演示:在页面找到显示当前登录用户的地方,然后将《a》和《a》之间的内容替换成以下行内写法:
<a href="javascript:;">[[${session.loginUser.username}]]</a>
然后页面测试:
抽取thymleaf公共页面:
在我们的模板中,我们经常会希望包含其他模板中的部分,例如页脚,页眉,菜单等部分。
为了做到这一点,Thymeleaf需要我们定义这些要包含的部分“片段”,这可以使用该th:fragment属性来完成。
注意:抽取公共页面的时候也要构建一个完整的页面:包含html,head,body等。
以后遇到地址都可以使用thymeleaf的写法:@{/} /后写地址,这样以后我们写地址,可以给我们动态加上项目名。很方便,以后部署项目的时候,如果需要改变项目名,我们都不需要改变源代码;
假设我们创建了一个公共的页面common.html,包含每个页面的页眉,页脚,和导航栏等。
如下:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div th:fragment="footerside">
© 2011 The Good Thymes Virtual Grocery
</div>
</body>
</html>
上面的代码定义了一个片段footerside,我们可以使用th:insert或th:replace属性之一轻松地将其包含在主页中.如下(其中common 为公共页面的文件名):
<body>
...
<div th:insert="common :: footerside}"></div>
</body>
片段规范语法
th:insert和th:replace(和th:include)之间的差异:
和之间有什么区别th:insert和th:replace(和th:include,因为3.0不推荐)
th:insert 最简单:它将简单地将指定的片段作为其主机标签的主体插入(即把片段标签整个插入到引用处内部)。
th:replace实际上将其主机标签替换为指定的片段。
th:include与相似th:insert,但不插入片段,而是仅插入该片段的内容。
代码演示:
<footer th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</footer>
<body>
...
<div th:insert="footer :: copy"></div>
<div th:replace="footer :: copy"></div>
<div th:include="footer :: copy"></div>
</body>
将导致:
<body>
<div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
</div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
<div>
© 2011 The Good Thymes Virtual Grocery
</div>
</body>
注意:我们只抽取公共的js,css,其他不是公共的js保留在原页面。原页面的js等前面都带/,common页面js等带/,如@{/js/jquery-1.10.2.min.js}
错误总结:抽取公共页面是一个体力活,也是一个细心活,不用忘了th:等,当修改完错误后我们使用maven的插件中的clean清理一下缓存看效果。(遇到过这个坑)
common.html:
<div th:fragment="commonscript">
<!-- Placed js at the end of the document so the pages load faster -->
<script th:src="@{/js/jquery-1.10.2.min.js}"></script>
<script th:src="@{/js/jquery-ui-1.9.2.custom.min.js}"></script>
<script th:src="@{/js/jquery-migrate-1.2.1.min.js}"></script>
<script th:src="@{/js/bootstrap.min.js}"></script>
<script th:src="@{/js/modernizr.min.js}"></script>
<script th:src="@{/js/jquery.nicescroll.js}"></script>
<!--common scripts for all pages-->
<script th:src="@{/js/scripts.js}"></script>
</div>
引用:
<div th:replace="common :: commonscript"></div>
js的引用可以直接这样:我们不需要和th:href一样,有两个href,一个是链接静态的,一个是链接动态的。
<div id="commonscript">
<!-- Placed js at the end of the document so the pages load faster -->
<script th:src="@{/js/jquery-1.10.2.min.js}"></script>
<script th:src="@{/js/jquery-ui-1.9.2.custom.min.js}"></script>
<script th:src="@{/js/jquery-migrate-1.2.1.min.js}"></script>
<script th:src="@{/js/bootstrap.min.js}"></script>
<script th:src="@{/js/modernizr.min.js}"></script>
<script th:src="@{/js/jquery.nicescroll.js}"></script>
</div>
查看页面源代码:
修改公共页面的超链接等:
修改主页:
修改公共页面的用户名:找到dropdown下的管理员,并用行内元素的写法 [[${取出session中的值}]]
退出功能( session.invalidate()):
@GetMapping({"/login","/"})
public String toLogin(){
return "login";
}
//注销
@GetMapping("/logout")
public String logout(HttpSession session){
//清除session。invalidate:作废
session.invalidate();
return "redirect:/login";//让其重定向到登录页面
}
表单遍历:
页面遍历取值:先在controller中向数据模型中存值:
model.addAttribute(“users”,users);
@GetMapping("/basic_table")
public String basic_table(Model model){
List<LoginUser> users = loginUserMapper.getLoginUsers();
System.out.println(users);
model.addAttribute("users",users);
return "table/basic_table";
}
然后页面遍历值:
<tbody>
<tr th:each="user,stat: ${users}">
<td th:text="${stat.count}">3</td>
<td th:text="${user.id}">Larry</td>
<td th:text="${user.username}">the Bird</td>
<td th:text="${user.password}">@twitter</td>
</tr>
</tbody>
添加状态:行号等
拦截器:
springboot中的拦截器使用:
第一步:编写拦截器,写一个拦截器的包interceptor,并编写登录的拦截器:
拦截器方法说明:
preHandle:在请求处理之前进行调用(Controller方法调用之前)
postHandle:请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)
afterCompletion:在整个请求结束之后被调用,也就是在DispatcherServlet 渲染了对应的视图之后执行(主要是用于进行资源清理工作)
拦截器类实现HandlerInterceptor 接口:
package com.fan.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
Object loginUser = session.getAttribute("loginUser");
System.out.println("拦截器:"+loginUser);
if(loginUser != null){
return true;
}
//不符合条件的给出提示信息,并转发到登录页面
request.setAttribute("msg","被拦截器拦截,请先登录");
//转发到/个请求
request.getRequestDispatcher("/").forward(request,response);
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
2.将我们的拦截器放到容器中,并配置拦截规则:注意@Configuration要写到类上。
package com.fan.config;
import com.fan.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**")//要拦截的路径,我们这里拦截所有
.excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**");//哪些不需要拦截的资源路径
}
}
转发与重定向页面跳转的区别:
在进行web开发时,跳转是最常见的,今天在这里来学习下2种跳转:
第一种是request.getRequestDispatcher().forward(request,response):
1、属于转发,也是服务器跳转,相当于方法调用,在执行当前文件的过程中转向执行目标文件,两个文件(当前文件和目标文件)属于同一次请求,前后页共用一个request,可以通过此来传递一些数据或者session信息,request.setAttribute()和request.getAttribute()。
2、在前后两次执行后,地址栏不变,仍是当前文件的地址。
3、不能转向到本web应用之外的页面和网站,所以转向的速度要快。
4、URL中所包含的“/”表示应用程序(项目)的路径。
第二种是response.sendRedirect():
1、属于重定向,也是客户端跳转,相当于客户端向服务端发送请求之后,服务器返回一个响应,客户端接收到响应之后又向服务端发送一次请求,一共是2次请求,前后页不共用一个request,不能读取转向前通过request.setAttribute()设置的属性值。
2、在前后两次执行后,地址栏发生改变,是目标文件的地址。
3、可以转向到本web应用之外的页面和网站,所以转向的速度相对要慢。
4、URL种所包含的"/"表示根目录的路径。
特殊的应用:对数据进行修改、删除、添加操作的时候,应该用response.sendRedirect()。如果是采用了request.getRequestDispatcher().forward(request,response),那么操作前后的地址栏都不会发生改变,仍然是修改的控制器,如果此时再对当前页面刷新的话,就会重新发送一次请求对数据进行修改,这也就是有的人在刷新一次页面就增加一条数据的原因。
如何采用第二种方式传递数据:
1、可以选择session,但要在第二个文件中删除;
2、可以在请求的url中带上参数,如"add.htm?id=122"
在类上使用@Slf4j打日志:
放行静态资源:
/css可以理解为static文件下的css文件夹,其他类似:
第二种方式:配置静态资源的访问路径:
推荐第一种,因为第二种方式每一个页面都要改静态资源的路径。
总结拦截器的使用:
看拦截器的执行顺序:
然后登陆测试,再直接访问main页面测试;
代码演示;
编写自定义的拦截器LoginIntercepter :
package com.fan.admin.intercepter;
import com.fan.admin.entity.LoginUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@Slf4j
@Configuration
public class LoginIntercepter implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler
) throws Exception {
String requestURI = request.getRequestURI();
log.info("preHandle拦截器的请求路径是{}",requestURI);
//登录检查的逻辑
HttpSession session = request.getSession();
LoginUser loginUser = (LoginUser) session.getAttribute("loginUser");
if(loginUser != null){
return true;
}
//拦截住未登录,跳转到登录页
session.setAttribute("msg","请先登录");
//使用转发来获取session中的值
request.getRequestDispatcher("/").forward(request,response);
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandle执行{}",modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("afterCompletion执行异常{}",ex);
}
}
配置拦截器(将自定义的拦截器注册到容器中):
package com.fan.admin.config;
import com.fan.admin.intercepter.LoginIntercepter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
public class AdminWebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginIntercepter())
.addPathPatterns("/**")
.excludePathPatterns("/","login","/css/**",
"/fonts/**","/images/**","/js/**");
}
}
拦截器的源码探究:
如果当前拦截器返回false:
拦截器总结:
文件上传:
文件上传的使用:
修改文件上传表单页面:
form表单的请求和类型等:
代码:
<form role="form" method="post" enctype="multipart/form-data" th:action="@{/upload}">
<div class="form-group">
<label for="exampleInputEmail1">邮箱</label>
<input name="email" type="email" class="form-control" id="exampleInputEmail1" placeholder="请输入邮箱">
</div>
<div class="form-group">
<label for="exampleInputPassword1">上传者</label>
<input name="uploadusername" type="text" class="form-control" id="exampleInputPassword1" placeholder="请输入上传者名字">
</div>
<div class="form-group">
<label for="exampleInputFile">头像</label>
<input name="headerimg" type="file" enctype="multipart/form-data" id="exampleInputFile">
<p class="help-block">单文件上传</p>
</div>
<div class="form-group">
<label for="exampleInputFile2">生活照</label>
<input name="photos" id="exampleInputFile2" type="file" enctype="multipart/form-data" multiple>
<p class="help-block">多文件上传</p>
</div>
<div class="checkbox">
<label>
<input type="checkbox"> Check me out
</label>
</div>
<button type="submit" class="btn btn-primary">上传</button>
</form>
controller:
//文件上传
@PostMapping("/upload")
public String upload(
@RequestParam("email") String email,
@RequestParam("uploadusername") String uploadusername,
@RequestParam("headerimg") MultipartFile headerimg,
@RequestParam("photos") MultipartFile[] photos
) throws IOException {
log.info("上传的信息:email={},uploadusername={},headerimg={},photos={}",
email,uploadusername,headerimg,photos.length);
//单文件上传
if(!headerimg.isEmpty()){
//保存到文件服务器
String originalFilename = headerimg.getOriginalFilename();
//注意a文件夹要存在的
headerimg.transferTo(new File("g:\\a\\"+originalFilename));
}
//多文件上传
if(photos.length>0){
for (MultipartFile photo : photos) {
if(!photo.isEmpty()){
String originalFilename = photo.getOriginalFilename();
//注意a文件夹要存在的
photo.transferTo(new File("g:\\a\\"+originalFilename));
}
}
}
return "index";
}