文章目录
项目源码:链接:https://pan.baidu.com/s/1mK7vg_KiifbBUO15ylUOFA
提取码:t9lx
1. SpringMVC框架的作用
MVC = Model(数据模型) + View(视图) + Controller(控制器),MVC思想是建议每个项目中至少有这3种核心的角色,用于处理不同的问题,其中,Model表示的数据处理,例如数据的增删改查等,View表示软件的界面,Controller表示控制器,用于接收客户端提交到服务器端的请求,并在处理完请求后给予客户端响应结果。
在不同的技术领域,这种思想可能也不同,例如在一些客户端软件的开发中,会使用MVP、MVVP等思想。
SpringMVC框架是基于Spring框架的,所以,在SpringMVC中,会使用到Spring框架的作用,例如通过Spring框架来创建对象、管理对象,也会使用到Spring框架中的一些注解。
SpringMVC框架主要解决了V-C交互的问题,即:客户端将请求提交到服务器后,肯定是由服务器端的控制器接收请求,SpringMVC就解决了如何接收请求(包含请求中的参数等)的问题,当服务器端处理完请求之后,应该给予客户端响应结果,SpringMVC也解决了如何响应的问题!事实上,SpringMVC框架与“M”完全没有任何关系,所以,在SpringMVC的学习过程中,完全不考虑数据的增删改查相关数据库技术。
2. 为什么要使用SpringMVC框架
在原生的Java EE技术体系中,处理用户请求的是Servlet
组件,通常情况下,每个Servlet
组件处理1种请求,例如“用户注册”的请求可能由UserRegServlet
来处理,“用户登录”的请求可能由UserLoginServlet
来处理……在比较复杂的业务系统中,用户提交的请求的种类可能特别多,就会导致Servlet
组件的数量也特别多!进而导致代码的管理难度很大,同时,在项目运行时,诸多的Servlet
组件也会占用较多的内存空间……
在SpringMVC框架的设计中,就解决了以上问题,它使用1个DispatcherServlet
的组件,用于接收所有请求(当然,也可以配置为某些特定的请求,例如配置为仅处理*.do
的请求),以此减少Servlet
组件的数量!
原本Servlet
是用于处理请求的,而在SpringMVC框架中,DispatcherServlet
的主要作用是接收到请求后,分发到具体处理请求的Controller
组件,其本身并不处理请求!而SpringMVC中的每个Controller
组件都可以有若干个处理请求的方法,也就是每个Controller
组件都可以处理若干种请求,所以,即使项目很复杂,请求的种类很多,但是,Controller
组件的数量并不会太多!
DispatcherServlet
与Controller
的关系就好比银行大厅的取号机和业务柜台的关系。
SpringMVC框架的使用比原生的Java EE更加简单!
3. SpringMvc执行流程
-
DispatcherServlet:前端控制器,用于接收所有请求
-
HandlerMapping:用于配置请求与运行的方法的对应关系
-
Controller:控制器,这种对象时我们编写处理具体功能的
-
ModelAndView:控制器完成请求处理后,的处理结果一般是指定页面名称
-
ViewResolver:视图解析器,根据给定的页面名称,生成页面内容
-
4. 项目创建以及相关pom依赖以及使用SpringMVC框架处理请求
- 打开idea我们创建一个maven项目
项目整体结构
步骤1:我们修改一下web.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
步骤2:
添加pom.xml文件中 SpringMvc以及其他相关内容的依赖
<!-- Spring MVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
<!-- Thymeleaf -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>3.0.11.RELEASE</version>
</dependency>
<!-- Thymeleaf整合Spring -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.11.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
步骤3:
创建一个包(cn.tedu.config)
在这个包新建一个WebApp类
代码如下
//AbstractAnnotationConfigDispatcherServletInitializer
//是SpringMVC提供的,它包含几个必须由我们重写并配置SpringMvc运行信息的方法
//AACDSI打出这几个字母idea自动显示 AbstractAnnotationConfigDispatcherServletInitializer 是SpringMvc提供的
//它包含几个必须由我们重写并配置SpringMvc运行信息的方法
public class WebApp extends
AbstractAnnotationConfigDispatcherServletInitializer {
// 继承的这个抽象类有一个特征
// tomcat一旦启动就会自动运行下面的三个方法
//RootConfig配置和控制器无关的Spring配置类
@Override
protected Class<?>[] getRootConfigClasses() {
System.out.println("111111");
return new Class[0];
}
//ServletConfig配置和控制器相关的Spring配置类
@Override
protected Class<?>[] getServletConfigClasses() {
System.out.println("222222");
return new Class[0];
}
//getServletMapping是指定SpringMvc对什么样的请求进行处理的方法
//配置*.do 表示所有以.do结尾的请求,都会交由SpringMvc处理
@Override
protected String[] getServletMappings() {
System.out.println("333333");
return new String[0];
}
}
启动tomcat会看到111,222,333的输出
证明现在SpringMvc框架配置是正确的
步骤4:
配置SpringMvc的信息
有两方面信息要配置
1.请求的拦截规则 我们暂时规定 以".do"结尾的请求由SpringMvc接收处理
2.指定Spring的配置类,配置的是配置类的反射
//AbstractAnnotationConfigDispatcherServletInitializer
//是SpringMVC提供的,它包含几个必须由我们重写并配置SpringMvc运行信息的方法
public class WebApp extends
AbstractAnnotationConfigDispatcherServletInitializer {
// 继承的这个抽象类有一个特征
// tomcat一旦启动就会自动运行下面的三个方法
//RootConfig配置和控制器无关的Spring配置类
@Override
protected Class<?>[] getRootConfigClasses() {
System.out.println("111111");
return new Class[0];
}
//ServletConfig配置和控制器相关的Spring配置类
@Override
protected Class<?>[] getServletConfigClasses() {
System.out.println("222222");
return new Class[]{SpringMvcConfig.class};
}
//getServletMappings是指定SpringMvc对什么样的请求进行处理的方法
//配置*.do 表示所有以.do结尾的请求,都会交由SpringMvc处理
@Override
protected String[] getServletMappings() {
System.out.println("333333");
return new String[]{"*.do"};
}
}
补充:
由于AbstractAnnotationConfigDispatcherServletInitializer
是抽象类,所以,继承之后,需要重写其中的3个抽象方法:
getRootConfigClasses()
:获取Spring的配置类,在简单的SpringMVC项目中,也许并不需要编写任何Spring环境的配置,则该方法返回null
即可,后续,如果整合MyBatis框架或其它框架时,可能需要编写相关配置,我们return new Class[0]作用与null
无区别;getServletConfigClasses()
:获取SpringMVC的配置类,SpringMVC的配置类可以自定义,且必须实现WebMvcConfigurer
接口;getServletMappings()
:获取框架所处理的请求的路径,假设所有以.do
为后缀的请求都需要被SpringMVC框架处理,则可以配置为*.do
。- 以上
SpringMvcInitializer
类的作用就类似于web.xml的作用!例如,后续,当项目中需要使用到Filter
组件时,也可以在这个类中添加配置!
步骤5:
创建配置类
SpringMvcConfig类
只需要添加一个扫描的包就可以了
@ComponentScan("cn.tedu.controller")
public class SpringMvcConfig {
}
步骤6:
创建cn.tedu.controller包
在这个包中新建一个控制器DemoController
//使用@Controller注解这个类
//能够将这个类注入到Spring容器的同时还能表明它是一个控制器
@Controller
public class DemoController {
// 编写@GetMapping注解来规定什么请求能够访问这个方法
// 我们配置了只有.do结尾的请求能够进入SpringMvc所以这里的路径必须以.do结尾
@GetMapping("/hello.do")
// 我们由于没有具体页面可以显示,所以需要添加下面的注解
// 表示当前请求的结果就是在浏览器上显示返回的字符串
@ResponseBody
public String demo(){
System.out.println("demo运行");
return "Hello SpringMvc";
}
}
5. SpringMvc实现显示视图
SpringMvc显示视图也是依赖Thymeleaf
Thymeleaf也是哦Spring框架推荐使用的模板引擎,使用率是比较高的
我们已经在上面的配置中包含了pom.xml的依赖的配置
我们直接将Thymeleaf模板引擎注入到Spring容器中即可
在SpringMvcConfig类中获得代码如下
注意注入的代码不是自己编写的,不用了解具体每行代码的功能
@ComponentScan("cn.tedu.controller")
public class SpringMvcConfig {
@Bean
public ThymeleafViewResolver viewResolver(){
System.out.println("init ThymeleafViewResolver");
//设置模板保存位置为 /resources/templates/*.html
ClassLoaderTemplateResolver templateResolver =
new ClassLoaderTemplateResolver();
//模版存储文件夹
templateResolver.setPrefix("/templates/");
//模版后缀
templateResolver.setSuffix(".html");
templateResolver.setCharacterEncoding("UTF-8");
templateResolver.setTemplateMode(TemplateMode.HTML);
templateResolver.setCacheable(true);
//创建模板引擎
SpringTemplateEngine templateEngine =
new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
templateEngine.setEnableSpringELCompiler(true);
//创建模版解析器
ThymeleafViewResolver viewResolver =
new ThymeleafViewResolver();
viewResolver.setCharacterEncoding("UTF-8");
viewResolver.setTemplateEngine(templateEngine);
return viewResolver;
}
}
需要注意,我们模板文件(html)文件
必须放在resources/templates/文件夹下,没有的话需要创建!
创建了templates文件夹后可以在其中创建一个html文件
代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>这是SpringMvc和Thymeleaf显示的页面</h1>
</body>
</html>
下面我就可以使用SpringMvc来访问这个页面了
控制器中新加一个方法来返回这个页面
代码如下
@GetMapping("/test.do")
public ModelAndView test(){
System.out.println("运行了test");
//返回的hello会被模板引擎自动添加配置好的前缀和后缀
//resources/templates/hello.html
return new ModelAndView("hello");
}
我们想让SpringMvc有用武之地需要表单来体现一下
创建一个带有表单的页面:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>用户注册</title>
</head>
<body>
<h1>注册</h1>
<form method="post" action="handle_reg.do">
<div>
<label>用户</label>
<input type="text" name="username">
</div>
<div>
<label>密码</label>
<input type="password" name="password">
</div>
<div>
<label>年龄</label>
<input type="number" name="age">
</div>
<div>
<label>电话</label>
<input type="tel" name="phone">
</div>
<div>
<label>电邮</label>
<input type="email" name="email">
</div>
<div>
<input type="submit" value="提交">
</div>
</form>
</body>
</html>
习题:再编写一个方法
使用reg.do的路径让这个表单页面显示在浏览器上!
6. 使用SpringMvc接收表单信息
实现上面的功能新建一个控制器
UserController代码如下
//@RestController和@Controller的区别
//@RestController除了Controller注解的功能将当前类注入到Spring容器之外
//所有返回字符串的方法,都是直接将字符串返回相当于加了@ResponseBody
//如果想返回页面,直接返回ModelAndView对象即可
@RestController
//@RequestMapping()写在类上,表示要访问这个类中的方法,需要统一前置增加指定内容
//比如("/user") 就表示要想访问这个类中的方法需要先写"/user"
@RequestMapping("/user")
public class UserController {
//@GetMapping("/abc.do")//localhost:8080/user/abc.do
//...
@GetMapping("/reg.do")//localhost:8080/user/reg.do
public ModelAndView reg(){
return new ModelAndView("reg");
}
}
显示表单以后我们想提交信息到控制器
就像之前在Servlet类中获得表单中的信息一样
但是SpringMvc控制器完成这个操作比Servlet简单很多
代码如下
//接收表单信息的方法
//@PostMapping来接受post请求
@PostMapping("/handle_reg.do")
public String handReg(
String username,
String password,
int age,
String phone,
String email
){
System.out.println("用户名:"+username);
System.out.println("密码:"+password);
System.out.println("年龄:"+age);
System.out.println("电话:"+phone);
System.out.println("电邮:"+email);
return "OK";
}
需要注意上面的方法接收的请求是Post请求所以使用@PostMapping来接收
方法的参数必须严格按照对应表单中name属性的值来获得用户输入的信息
类型会自动转换(int age直接获取即可)
虽然这样的代码已经比Servlet简单了,但是开发大型项目可能又十几甚至更多属性的表单
我们可以再简单一些
新建一个包,包中新建一个User类
代码如下
public class User implements Serializable {
private String username;
private String password;
private int age;
private String phone;
private String email;
//省略set\get\toString
}
上面定义的类中的属性名和表单的name属性值对应
有了这个类我们就可以更简单的获得表单中的信息
代码如下
@PostMapping("/handle_reg.do")
public String handReg(User user){
System.out.println(user);
return "OK";
}
常见问题解答
1.User类对象时怎么接收到表单信息的
本质上是DispatcherServlet检测Controller方法属性的
内部是同属性的set方法为对象赋值的
赋值原则是尽量赋值
2.路径问题
/user是怎么保持的
访问/user/reg.do就已经进入了/user/目录下
表单提交action="handle_reg.do"是相对路径,是在/user/下提交的
提交结果是/user/handle_reg.do ,所以不需要编写前置的/user/
7.表单提交的中文信息处理
上面提交信息到Controller中如果是中文会发生乱码
为了防止乱码,回到WebApp类中添加一个方法,设置过滤器即可
//这个方法不是必须重写的
//但是表单提交中文有乱码
//添加一个过滤器防止乱码的出现
@Override
protected Filter[] getServletFilters() {
return new Filter[]{new CharacterEncodingFilter("UTF-8")};
}
8.控制器方法获得Request对象
我们可能仍然偶尔需要HttpServletRequest类的方法
name怎么在现有的控制器方法中获得这个对象呢?
代码如下
@PostMapping("/handle_reg.do")
public String handReg(User user, HttpServletRequest request){
System.out.println(user);
//输出客户端的ip地址
System.out.println("访问服务器的客户端地址为:"+request.getRemoteAddr());
return "OK";
}
直接再方法的参数列表中声明HttpServletRequest类型的变量即可
DispatcherServlet会自动将当前请求对象赋值为这个属性!
9.控制器方法接收get请求参数
get请求参数一般都是以url中?开始的
例如
http://localhost:8080/deleteUser.do?id=10&name=tom
控制器中编写代码,原理和写法和接收表单信息是一致的
//接收get请求参数的方法
@GetMapping("/get.do")
//参数的参数名必须和?后面的名称一致
public String get(int id){
System.out.println("id:"+id);
return "ok";
}
10.特殊参数名处理
当get请求参数名称是java关键字时,必须使用@RequestParam进行注解
代码如下
@GetMapping("/get.do")
//如果get请求参数名是特殊字符\java关键字name参数名称就无法直接对应
//需要在参数前添加@RequestParam注解
// 这个注解可以指定后门参数对象的get请求参数名
public String get(@RequestParam("if") int num){
System.out.println("num:"+num);
return "ok";
}
11.控制器向页面传递数据
前面我们学习了怎么在控制器中接收表单提交过来的信息
下面我们要学习怎么将控制器中的信息,发送到页面上显示
我们以下面的简单业务为例
一个登录页面,输入用户名和密码
根据登录成功或失败在一个页面上显示信息
利用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相关,综合效果好,推荐使用
12.转发与重定向
控制器中实际上有两种使页面跳转的方式
分别是转发和重定向
什么是转发
我们上面学习的课程中使用的就都是转发来跳转页面的
转发:是服务器内部的行为,是由服务器端的控制器将请求转发到视图组件的过程,由于整个过程是发生在服务器内部的,所以,对于客户端来说,是不知道这个过程的,在客户端的浏览器的地址栏中的URL也就一直是最初发出请求的URL,在整个过程中,客户端也只发出了1次请求,如果刷新页面,会再次提交请求,从代码方面来看,转发时,控制器需要给出的只是“视图名称”即可,然后,根据服务器内部的相关配置确定具体的视图组件,之所以是这样,还是因为“转发是服务器内部的行为”,结合服务器端的配置一起使用也是非常正常的!由于转发是服务器内部端内部的行为,所以,服务器端的控制器可以转发任何数据到视图组件!
或者举个例子,甲向李四借钱,却不直接和李四说,而是甲和张三说要向李四借钱,然后张三再与李四说,并把从李四哪里借到的钱给甲!(图中左边蓝色的小人是甲)
什么是重定向
重定向实际上是http协议提供的功能
客户端向服务器发送请求后,服务器向客户端响应302状态码
浏览器接收到302状态码后,立即向服务器指定的url发送新的请求
这就是重定向的过程
复杂的说重定向:在整个过程中,客户端发出第1次请求时,服务器的响应方式是重定向,其具体表现是服务器端会向客户端发出302
HTTP响应码,表示“重定向”,同时,还会向客户端响应目标路径,当客户端收到响应码是302
时,就会自动的发出第2次请求,并根据服务器端响应的目标路径发出请求。由于客户端是明确第2次请求的目标的,所以,在客户端的浏览器的地址栏中会显示第2次请求的URL。从代码方面来,重定向时,必须给出明确的目标路径,客户端将根据这个路径发出第2次请求!由于前后共有2次请求,同时基于HTTP协议是无状态协议,在没有结合其它技术时,服务器端处理第1次请求时得到的数据并不可以用于处理第2次请求。
Demo代码如下
/**
* 演示重定向的控制器
*/
@RestController
@RequestMapping("/doc")
public class DocController {
@GetMapping("/doc.do")
public ModelAndView doc(){
System.out.println("重定向到百度");
return new ModelAndView(
"redirect:https://www.baidu.com/");
}
}
当我们输入url:localhost:8080/doc/doc.do时
我们看到了百度
代码中我们返回的ModelAndView中
字符串以"redirect:"开头,DispatcherServlet在接收到这样的控制器返回结果时
就是进行重定向操作
什么时候使用重定向呢?
只要当前控制器的跳转目标不是我们的视图模板,就使用重定向
原因是SpringMvc中使用转发只能访问resources/templates/下的html文件
重定向和转发的区别
常见面试题:
1.请求次数上
转发过程中只出现一次请求
重定向过成中有两次请求
2.地址栏区别
转发后显示的是浏览器初始时访问的控制器路径
重定向显示的是重定向后新的请求发送给的路径
3.作用域区别
转发因为是一次请求,转发的页面和请求的路径共享同一个request的数据
重定向是两次请求,请求的页面和重定向的页面不能共享同一个request的数据
13. 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");
}
14.拦截器
什么是拦截器
是SpringMvc提供的组件
可以使得指定路径的请求被处理时,运行拦截器中的代码
而且拦截器中可以决定这个请求继续执行还是阻止执行
举例:
为什么使用拦截器
上面的图片表示一个项目中有很多请求
有的请求可以直接放行,显示目标页面的内容
但是例如"我的账户"这样的请求必须先登录才能访问
而且这样先登录才能访问的页面不止一个
使用拦截器将所有需要登录才能访问的页面控制起来,方便权限管理
怎么使用拦截器
首先来编写一个拦截器,了解它最基本的使用
新建一个拦截器包
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");
15.拦截器和过滤器的区别
1.拦截器是SpringMvc框架的组件,而过滤器是java提供的API
2.拦截器只能拦截目标为DispatcherServlet的请求
过滤器范围广,过滤目标可以是任何资源
3.功能上拦截器功能更强,和SpringMvc配合更方便
4.过滤器Filter
是Java EE中的组件,只要是Java Web项目都可以使用过滤器,包括在SpringMVC项目中也可以使用过滤器;拦截器Interceptor
是SpringMVC框架中的组件,只有使用了SpringMVC框架的项目,才可以使用拦截器,并且,只有被SpringMVC框架处理的请求才可能被拦截器所处理,例如,将DispatcherServlet
映射的路径(在SpringMvcInitializer
中getServletMappings()
方法的返回值)配置为*.do
时,只有以.do
为后缀的请求才可能被拦截器处理,而例如.jpg
类似的请求将不会被拦截器处理,当然,如果有必要的话,也可以将DispatcherServlet
映射的路径配置为/*
,则任何请求都可能被拦截器处理。
5.过滤器Filter
可以配置若干个映射路径,可以使用通配符,但是,却不能从中剔除部分请求路径,也就是“不能配置白名单”,在配置时可能比较麻烦;拦截器Interceptor
在配置映射的请求路径时,既可以配置拦截路径(黑名单),又可以配置排除路径(白名单),配置更加简单、灵活。
6.过滤器Filter
是执行在所有Servlet
组件之前的;而拦截器Interceptor
的第1次执行是在DispatcherServlet
之后,且在Controller
之前的。
有点啰嗦望见谅!
小结
如果实现http协议级别的基本拦截过滤,就使用过滤器
如果实现对SpringMvc控制器代码的拦截过滤,就使用拦截器
在SpringMVC项目中,应该优先使用拦截器Interceptor
组件,除非某些代码片断必须在Servlet
之前执行!
16.SpringMVC阶段小结
- 【理解】SpringMVC框架的作用:主要解决了如何接收请求、如何给予响应的问题;
- 【理解】SpringMVC的核心执行流程(参考流程图);
- 【掌握】创建SpringMVC项目:配置pom.xml使得没有web.xml也不会报错,且添加
spring-webmvc
依赖,勾选Tomcat,创建启动入口类并重写其中的3个抽象方法,创建SpringMVC的配置类; - 【掌握】使用控制器处理客户端提交的请求:控制器类必须放在组件扫描的包或其子孙包中,控制器类必须添加
@Controller
注解,处理请求的方法之前需要添加@RequestMapping
或对应更高级的注解来配置请求路径,方法应该是public
权限的,返回值暂时是String
类型表示“视图名称”,方法名称可以自定义,方法的参数列表可以按需设计; - 【掌握】当结合使用Thymeleaf时,需要先添加
thymeleaf
、thymeleaf-spring4
或thymeleaf-spring5
依赖,并在SpringMVC的配置中类配置Thymeleaf的视图解析器; - 【掌握】接收请求参数的方式:直接将请求参数声明为处理请求的方法的参数,或将若干个请求参数封装起来并使用封装的类型作为处理请求的方法的参数;
- 【掌握】将控制器中的数据转发到视图组件;
- 【理解】转发与重定向的区别,并掌握正常的选取;
- 【掌握】使用Session;
- 【掌握】开发并配置拦截器;
- 【掌握】
@RequestMapping
及相关高级注解的使用。