控制器向页面传递数据
上次课程中我们学习了怎么在控制器中接收表单提交过来的信息
下面的课程我们要学习怎么将控制器中的信息,发送到页面上显示
我们以下面的简单业务为例
一个登录页面,输入用户名和密码
根据登录成功或失败在一个页面上显示信息
利用Request对象实现传递数据
步骤1:创建登录页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>登录</h1>
<form method="post" action="handle_login.do">
<div>
<label>用户</label>
<input type="text" name="username">
</div>
<div>
<label>密码</label>
<input type="password" name="password">
</div>
<div>
<input type="submit" value="提交">
</div>
</form>
</body>
</html>
步骤2:
编写一个控制器方法将这个页面显示出来
@RestController
@RequestMapping("/home")
public class HomeController {
//显示登录页面的方法
@GetMapping("/login.do")
//localhost:8080/home/login.do
public ModelAndView showLogin(){
return new ModelAndView("login");
}
}
步骤3:
在执行登录操作前,先准备好显示登录成功或失败信息的页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>消息</h1>
<p th:text="${message}"></p>
</body>
</html>
步骤4:
编写控制器代码,判断登录成功或失败并给出相应信息
//接收表单提交的登录信息判断登录结果,并返回信息
// 由于我们要使用request对象传递信息,所以需要声明它
@PostMapping("/handle_login.do")
public ModelAndView handleLogin(
String username, String password,
HttpServletRequest request){
//使用request.setAttribute()方法向页面传递信息
//setAttribute(key,value)页面上出现的th:标签指定的内容
//会对应setAttribute方法中的key,显示这个key对应的value
if("tom".equals(username)){
if("123".equals(password)){
request.setAttribute("message","登录成功");
return new ModelAndView("message");
}else{
request.setAttribute("message","密码错误");
return new ModelAndView("message");
}
}else{
//传递信息:message指的是页面中th:text指定的key
request.setAttribute("message","用户名错误");
//返回视图:这里的message指定的是message.html页面
return new ModelAndView("message");
}
}
我们使用request对象进行了控制器到页面信息的传递
但是这样做使控制器耦合了ServletAPI
SpringMvc的封装就没有意义了,违背了SpringMvc的设计理念
而且不方便测试,所以实际开发中不推荐使用
利用ModelAndView传递数据
使用ModelAndView类提供的addObject(key,Value)方法
也能从控制器向页面传递数据
代码如下
//使用ModelAndView传递信息的方法
@PostMapping("/handle_login.do")
public ModelAndView handleLogin(
String username,String password){
if("tom".equals(username)){
if("123".equals(password)){
ModelAndView mv=new ModelAndView("message");
mv.addObject("message","登录成功");
return mv;
}else{
ModelAndView mv=new ModelAndView("message");
mv.addObject("message","密码错误");
return mv;
}
}else{
//使用ModelAndView对象的addObject方法添加信息
//信息格式也是键值对
ModelAndView mv=new ModelAndView("message");
mv.addObject("message","用户名错误");
return mv;
}
}
上面代码的问题是一旦完全依赖ModelAndView那么在传递信息时就必须先实例化它的对象
如果一个控制中有不同页面的跳转,需要实例化多个ModelAndView对象,代码比较多
使用ModelMap向页面传递数据
在控制器的方法中声明ModelMap
DispatcherServlet会获得这个ModelMap中的信息
以便传递给页面视图显示
//使用ModelMap传递信息的方法
@PostMapping("/handle_login.do")
public ModelAndView handleLogin(
String username, String password, ModelMap map){
if("tom".equals(username)){
if("123".equals(password)){
map.put("message","登录成功");
return new ModelAndView("message");
}else{
map.put("message","密码错误");
return new ModelAndView("message");
}
}else{
map.put("message","用户名错误");
return new ModelAndView("message");
}
}
这个方法是现在学习的三种方法中综合效果最好的
小结
1.request对象的传递方式:和ServletApi耦合,不好测试不推荐使用
2.ModelAndView:脱离了ServletApi但是代码比较多,可以使用
3.ModelMap:单独的一个map,只和DispatchServlet相关,综合效果好,推荐使用
习题:
1.创建一个页面,页面中有一个表单,表单中两个输入框 num1和num2两个数字
2.编写控制器方法显示这个页面
3.编写显示结果的页面使用Thymeleaf标签指定显示的位置
4.编写控制器方法处理表单的提交,使用ModelMap方法将num1和num2的和传递给页面
转发与重定向
控制器中实际上有两种使页面跳转的方式
分别是转发和重定向
什么是转发
我们上面学习的课程中使用的就都是转发来跳转页面的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SAdpCeCw-1606999153678)(S1MmkyBKG3wb589p.png!thumbnail)]
转发全程请求转发
是服务器内部对一个请求的偏转和发送,是一次请求
浏览器地址栏显示的是控制器的路径,显示的内容却是页面模板的内容
什么是重定向
重定向实际上是http协议提供的功能
客户端向服务器发送请求后,服务器向客户端响应302状态码
浏览器接收到302状态码后,立即向服务器指定的url发送新的请求
这就是重定向的过程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5FiGBAcM-1606999153681)(a0efy22E6ksxH4xp.png!thumbnail)]
上面图示我们编写一个控制器
控制器中将进行重定向操作
具体代码如下
/**
* 演示重定向的控制器
*/
@RestController
@RequestMapping("/doc")
public class DocController {
@GetMapping("/doc.do")
public ModelAndView doc(){
System.out.println("重定向到苍老师的网站");
return new ModelAndView(
"redirect:http://doc.canglaoshi.org");
}
}
当我们输入url:localhost:8080/doc/doc.do时
我们看到了苍老师的网站
代码中我们返回的ModelAndView中
字符串以"redirect:"开头,DispatcherServlet在接收到这样的控制器返回结果时
就是进行重定向操作
什么时候使用重定向呢?
只要当前控制器的跳转目标不是我们的视图模板,就使用重定向
原因是SpringMvc中使用转发只能访问resources/templates/下的html文件
重定向和转发的区别
常见面试题:
1.请求次数上
转发过程中只出现一次请求
重定向过成中有两次请求
2.地址栏区别
转发后显示的是浏览器初始时访问的控制器路径
重定向显示的是重定向后新的请求发送给的路径
3.作用域区别
转发因为是一次请求,转发的页面和请求的路径共享同一个request的数据
重定向是两次请求,请求的页面和重定向的页面不能共享同一个request的数据
SpringMvc中使用Session对象
我们编写的vrd项目登录后将用户信息保存在Session中
需要验证用户是否登录时直接检查session对象就可以了
SpringMvc中怎么控制管理Session呢?
在登录成功时将用户对象保存在Session里
代码如下:
//使用ModelMap传递信息的方法
@PostMapping("/handle_login.do")
public ModelAndView handleLogin(
String username, String password, ModelMap map,
HttpSession session){
//方法的参数中声明session对象
//DispatcherServlet就会自动将当前会话对象赋值到这个session中
if("tom".equals(username)){
if("123".equals(password)){
map.put("message","登录成功");
//登录成功,将用户保存在Session中
session.setAttribute("user",username);
return new ModelAndView("message");
}else{
map.put("message","密码错误");
return new ModelAndView("message");
}
}else{
map.put("message","用户名错误");
return new ModelAndView("message");
}
}
上面的代码在控制的方法中声明的HttpSession类型的参数
会自动赋值Session对象
在登录成功时,将用户名保存在Session中
下面创建一个页面来显示session中用户的信息
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>这里显示Session中保存的用户名</h1>
<p>欢迎您:<span th:text="${username}"></span></p>
</body>
</html>
这个页面是用来显示Session中的用户名的
还需要一个控制器来讲登录用户的信息传递到这个页面上
我们继续在HomeController中编写
代码如下
//显示已登录用户名的页面
@GetMapping("/show_name.do")
public ModelAndView showName(
HttpSession session,ModelMap map){
//先从session中获得用户名
String username=(String)session.getAttribute("user");
//再将获得的用户名赋值到ModelMap中用于显示在页面上
map.put("username",username);
//转发到视图
return new ModelAndView("welcome");
}
拦截器
什么是拦截器
是SpringMvc提供的组件
可以使得指定路径的请求被处理时,运行拦截器中的代码
而且拦截器中可以决定这个请求继续执行还是阻止执行
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zaUaYP8q-1606999153684)(372GmKyottgugtcE.png!thumbnail)]
为什么使用拦截器
上面的图片表示一个项目中有很多请求
有的请求可以直接放行,显示目标页面的内容
但是例如"我的账户"这样的请求必须先登录才能访问
而且这样先登录才能访问的页面不止一个
使用拦截器将所有需要登录才能访问的页面控制起来,方便权限管理
怎么使用拦截器
首先来编写一个拦截器,了解它最基本的使用
新建一个拦截器包
cn.tedu.interceptor
在这个包中新建一个拦截器类DemoInterceptor
代码如下
public class DemoInterceptor implements HandlerInterceptor {
//这个方法的运行时机是进入到控制器之前
//方法的返回值控制是否允许这个请求进入控制器
//返回true表示放行,返回false表示阻止
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
System.out.println("preHandle运行");
return true;
}
//控制器运行完毕之后运行
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("postHandler运行");
}
//在视图生成之后运行(浏览器还没有显示)
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
System.out.println("afterCompletion运行");
}
}
上面定义的拦截器还没有注册到Spring 中
下面通过编写配置类代码来注册拦截器
SpringMvcConfig类代码如下
@ComponentScan("cn.tedu.controller")
//@EnableWebMvc 表示当前配置类可以配置SpringMvc相关信息
//这里就是值可以配置拦截器
@EnableWebMvc
public class SpringMvcConfig implements WebMvcConfigurer {
//上面实现的接口也是表示当前的SpringMvcConfig类是可以配置拦截器的
//下面的方法就是专门注册拦截器到SpringMvc中的方法
//参数InterceptorRegistry就是注册拦截器的对象
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册一个拦截器,拦截/home/show_name.do
//即当访问/home/show_name.do路径时执行这个拦截器
registry.addInterceptor(new DemoInterceptor())
.addPathPatterns("/home/show_name.do");
}
//省略原有的其它代码
}
使用拦截器实现登录控制
我们现在有需求:
必须先登录才能访问/home/show_name.do
否则直接跳转到登录页面
下面我们来编写这个需求的拦截器
AccessInterceptor,代码如下
public class AccessInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
HttpSession session = request.getSession();
String username = (String) session.getAttribute("user");
System.out.println("拦截器获得session中用户名为:" + username);
//根据从session中获得的用户名决定当前请求是否可以继续访问目标控制器
if (username == null || username.isEmpty()) {
// 如果用户名是空,重定向到登录页
//request.getContextPath()获得结果等价于
//localhost:8080
String path=request.getContextPath()
+"/home/login.do";
response.sendRedirect(path);
return false;
}
System.out.println("已经登录,放行!");
return true;
}
}
在配置类中注册这个拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册一个拦截器,拦截/home/show_name.do
//即当访问/home/show_name.do路径时执行这个拦截器
registry.addInterceptor(new DemoInterceptor())
.addPathPatterns("/home/show_name.do");
//再注册一个拦截器
registry.addInterceptor(new AccessInterceptor())
.addPathPatterns("/home/show_name.do");
}
运行测试…
拦截器的详细配置
一个项目可能又很多url都需要先登录后才能访问
怎么配置拦截器拦截多个url?
代码如下
registry.addInterceptor(new AccessInterceptor())
.addPathPatterns(
"/home/show_name.do",
"/user/get.do",
"/doc/doc.do",
"/doc/chong.do");
addPathPatterns允许配置多个路径设置拦截
如果大型项目需要拦截的路径很多,写起来会很麻烦
所以拦截器的路径配置支持使用*通配
代码如下
//再注册一个拦截器
registry.addInterceptor(new AccessInterceptor())
.addPathPatterns(
"/home/show_name.do",
"/user/get.do",
"/doc/*",
"/cart/**");
/*
/doc/* 的配置可以拦截 /doc/chong.do /doc/doc.do
但是如果今后开发出现了类似 /doc/xxx/abc.do的多级目录
/doc/*是不能拦截的
只能使用/doc/** 这种写法 它可以拦截/doc/开头的所有请求
*/
使用通配设置拦截路径非常爽
但是如果这个路径下有个别请求需要单独放行,那么不好设置了
没关系,SpringMvc支持拦截器设置例外
即使在通配范围内的路径也可以访问
代码如下
registry.addInterceptor(new AccessInterceptor())
.addPathPatterns(
"/home/show_name.do",
"/user/get.do",
"/doc/*",
"/cart/**")
.excludePathPatterns(
"/doc/chong.do",
"/cart/add.do");