目录
4、@RequestMapping 中的 method 属性
5、@RequestMapping 中的 params 属性(了接)
1、使用ServletAPI 共享 request域中的数据
2、使用ModelAndView向request域对象共享数据
7、使用 ServletAPI 向 session 共享数据
8、使用 ServletAPI 向 application 共享数据
一、SpringMVC 简介
1、SpringMVC 基本概念
MVC 全称:Model 模型、 View 视图、 Controller 控制器。
MVC 最早出现在 JavaEE 三层中的 Web 层,它可以有效的指导 Web 层的代码如何有效分离,单独工作。
View 视图:只负责数据和界面的显示,不接受任何与显示数据无关的代码,便于程序员和美工的分工合作—— JSP/HTML。
Controller 控制器:只负责接收请求,调用业务层的代码处理请求,然后派发页面,是一个“调度者”的角色——Servlet 转到某个页面。或者是重定向到某个页面。
Model 模型:Model,模型层,指工程中的JavaBean,作用是处理数据
-
一类称为实体类Bean:专门存储业务数据的,如 Student、User 等
-
一类称为业务处理 Bean:指 Service 或 Dao 对象,专门用于处理业务逻辑和数据访问。
MVC 是一种思想
MVC 的理念是将软件代码拆分成为组件,单独开发,组合使用(目的还是为了降低耦合度)。
2、SpringMVC 的特点
-
Spring 家族原生产品,与 IOC 容器等基础设施无缝对接。
-
基于原生的Servlet,通过了功能强大的前端控制器 DispatcherServlet,对请求和响应进行统一处理
-
表述层各细分领域需要解决的问题全方位覆盖,提供全面解决方案
-
代码清新简洁,大幅度提升开发效率
-
内部组件化程度高,可插拔式组件即插即用,想要什么功能配置相应组件即可
-
性能卓著,尤其适合现代大型、超大型互联网项目要求
二、实现 Hello World
(1)、创建 基于 maven 的 web 工程。
(2)在 pom.xml 文件中 <dependencies>标签中 引入依赖
<!-- SpringMVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.1</version>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- ServletAPI -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- Spring5和Thymeleaf整合包 -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.12.RELEASE</version>
</dependency>
(3)、在 web.xml 文件中,配置 SpringMVC 前端控制器。
我们前面说了,SpringMVC 基于原生的 Servlet 的一个 前端控制器: DispatcherServlet。既然是 Servlet 我们就需要在 文件中配置。
《1》默认配置方式:再次配置下,SpringMVC 配置文件默认在 WEB-INF 目录下。
<!--配置 SpringMVC 前端控制器。 默认配置方式。-->
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<!--
设置 SpringMVC 前端控制器的的请求路径
/ :表示除了 .jsp 请求路径的请求,都能匹配。比如:/login,/a... .html .css .cs
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
《2》扩展配置方式:通过 <init-param> 标签设置 SpringMVC 配置文件的位置。
在 resources 目录下创建 SpringMVC 配置文件。
<!--扩展配置方式-->
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--初始化参数,在接受第一次请求的一些初始化操作-->
<init-param>
<!--上下文配置文件-->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:SpringMVC.xml</param-value>
</init-param>
<!--设置启动服务器时就加载配置文件
默认方式是接受第一次请求的时候才会执行初始化操作。
由于 SpringMVC 组件 初始化有大量的操作,所以尽量在提前操作,避免影响访问速度。
-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<!--
设置 SpringMVC 前端控制器的的请求路径
/ :表示除了 .jsp 请求路径的请求,都能匹配。比如:/login,/a... .html .css .cs
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
SpringMVC 配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--开启注解组件扫描-->
<context:component-scan base-package="controller"></context:component-scan>
<!-- 配置 Thymeleaf 视图解析器 -->
<bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
<property name="order" value="1"/>
<property name="characterEncoding" value="UTF-8"/>
<property name="templateEngine">
<bean class="org.thymeleaf.spring5.SpringTemplateEngine">
<property name="templateResolver">
<bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
<!-- 视图前缀 -->
<property name="prefix" value="/WEB-INF/templates/"/>
<!-- 视图后缀 -->
<property name="suffix" value=".html"/>
<property name="templateMode" value="HTML5"/>
<property name="characterEncoding" value="UTF-8" />
</bean>
</property>
</bean>
</property>
</bean>
</beans>
(4)、创建controller 层 或者 handler 层。并创建普通java类
@Controller //注解创建对象
public class HelloController {
// 访问:"WEB-INF/templates/index.html"
@RequestMapping(value = "/") //设置映射关系
public String index(){
//返回视图名称。由于我们 配置Thymeleaf视图解析器 的时候,配置了前缀是:templates 后缀是:.html
// 那么视图名称就是 index 。他会被 Thymeleaf视图解析器 解析 。
return "index";
}
//映射的网页地址。
@RequestMapping("/target")
public String hello(){
//视图名称
return "target";
}
}
(5)在 WEB-INF ---- templates 目录下创建俩个网页。
(6)总结:
《1》浏览器发送请求,若请求地址符合 前端控制器的 url-pattern,该请求就会被前端控制器DispatcherServlet处理。
《2》前端控制器会读取 SpringMVC 的核心配置文件,通过扫描组件找到控制器,将请求地址和控制器中 @RequestMapping 注解的 value 属性值进行匹配。
《3》若匹配成功,@Controller 所标识的控制器方法就是处理请求的方法。处理请求的方法需要返回一个字符串类型的视图名称,该视图名称会被视图解析器解析,加上前缀和后缀组成视图的路径,通过Thymeleaf对视图进行渲染,最终转发到视图所对应页面。
三、RequestMapping 注解
1、@RequestMapping 的 作用
@RequestMapping 注解的作用:将 请求 和 处理请求 的控制器方法关联起来,建立映射关系。
SpringMVC 接受到这个请求,会执行对应的方法进行处理请求。
2、@RequestMapping 的 位置
如果多个 controller 对同一个请求有相同的处理方法,就会报错。
这段错误表示 / 这个请求已经在 RequestMappingController 控制器中有了映射方法 index()
@RequestMapping标识一个类:设置映射请求的请求路径的初始信息。
@RequestMapping标识一个方法:设置映射请求请求路径的具体信息
如果我们在类和方法上都加上了 @RequestMapping 注解,那么在映射到 控制器中的方法时,需要将俩个路径拼接在一起。
这种方式还是 很重要的,举个例子:
比如:在一个项目中有 用户模块,还有订单模块。对应俩个 controller控制器。都需要使用 list 方法查询数据库。
那么前面说了,如果多个 controller 对同一个方法 进行请求处理。就会报错。所以我们需要在前面多加一层请求信息:
用户模块: / user / list
订单模块:/ order / list 这样就将俩个方法区分开了,当然在前端发送请求时,也需要加上这一层请求信息。
3、RequestMapping 中的 value 属性
@RequestMapping 注解的value属性通过请求的请求地址匹配请求映射
@RequestMapping 注解的value属性是一个字符串类型的数组,表示该请求映射能够匹配多个请求地址所对应的请求。
@RequestMapping 注解的value属性必须设置,至少通过请求地址匹配请求映射。其他属性写不写无所谓。
4、@RequestMapping 中的 method 属性
@RequestMapping 注解的method属性通过请求的请求方式(get或post)匹配请求映射。如果不写 method 属性,可以匹配所有的请求方式。
@RequestMapping 注解的method属性是一个RequestMethod类型的数组,表示该请求映射能够匹配多种请求方式的请求
若当前请求的请求地址满足请求映射的value属性,但是请求方式不满足method属性,则浏览器报错405:Request method 'POST' not supported
注:
1、对于处理指定请求方式的控制器方法,SpringMVC中提供了@RequestMapping的派生注解
处理get请求的映射-->@GetMapping
处理post请求的映射-->@PostMapping
处理put请求的映射-->@PutMapping
处理delete请求的映射-->@DeleteMapping
2、常用的请求方式有get,post,put,delete
但是目前浏览器只支持get和post,若在form表单提交时,为 method 设置了其他请求方式的字符串(put 或 delete),则按照默认的请求方式get处理
若要发送put和delete请求,则需要通过spring提供的过滤器 HiddenHttpMethodFilter,在RESTful部分会讲到
5、@RequestMapping 中的 params 属性(了接)
@RequestMapping注解的 params 属性通过请求的请求参数匹配请求映射
@RequestMapping注解的 params 属性是一个字符串类型的数组,可以通过四种表达式设置请求参数和请求映射的匹配关系
"param":要求请求映射所匹配的请求必须携带 param 请求参数
"!param":要求请求映射所匹配的请求必须不能携带 param 请求参数
"param=value":要求请求映射所匹配的请求必须携带param请求参数且 param=value
"param!=value":要求请求映射所匹配的请求必须携带param请求参数但是 param!=value
注:
若当前请求满足@RequestMapping注解的value和method属性,但是不满足params属性,此时页面回报错 400:HTTP Status 400 – Bad Request
6、SpringMVC 支持路径中的占位符 (重点)
原始方式:/deleteUser?id=1
rest方式:/deleteUser/1
其实就是将参数转换成请求路径的方式。
SpringMVC路径中的占位符常用于RESTful风格中,当请求路径中将某些数据通过路径的方式传输到服务器中,就可以在相应的@RequestMapping注解的value属性中通过占位符 {xxx} 表示传输的数据,在通过 @PathVariable 注解,将占位符所表示的数据赋值给控制器方法的形参
注:
如果在 @RequestMapping 中使用了占位符,在发送请求时也必须带参数,不然就会报:
HTTP Status 404 – Not Found 错误 。一个占位符有一个参数,有俩个占位符有俩个参数。。。
四、SpringMVC 获取请求参数
1、通过 ServletAPI 获取
bug提示:
如果一致显示 500 错误,并提示 springMVC.xml 不存在
查看你的 target/classes 目录下是否有 springMVC.xml 这个配置文件,如果没有,就把你 pom.xml 文件中的打包方式改成 war
<a th:href="@{/testServletAPI(username='jsck',password=1233)}">测试原生ServletAPI获取请求参数</a>
//通过原生的 HttpServletRequestAPI 获取请求参数
@RequestMapping("/testServletAPI")
//request 表示当前请求
//SpringMVC 已经把 HttpServletRequest 封装好了
public String servletAPI(HttpServletRequest request) {
String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.println("username = " + username + ",password = " + password);
return "success";
}
2、通过控制器方法的形参来获取请求参数
使用控制器方法来获取请求参数,只需要将 控制器方法的形参名称 与 请求参数的名称 保持一致,DispatcherServlet 会自动赋值
<form th:action="@{/testController}">
用户名:<input type="text" name="username" ><br>
密码 : <input type="password" name="password"><br>
爱好 : <input type="checkbox" name="hobby" value="smoke">smoke
<input type="checkbox" name="hobby" value="drink">drink
<input type="checkbox" name="hobby" value="tangtou">tangtou
<br>
<input type="submit" value="测试控制器获取请求参数">
</form>
//通过控制器传参,只需将形参名和请求中的参数名保持一致,就会自动赋值
@RequestMapping("/testController")
//如果获取多个 同名的请求参数,有俩种方式:
// 1、将形参名和请求参数名保持一致,他会自动将请求参数的值用 "," 拼接起来。
// 2、也是使用一个 String[] 的形式
// public String test_controller(String username,String password,String hobby){
public String test_controller(String username,String password,String[] hobby){
System.out.println("username = " + username +
",password = " + password + ",hobby = " + Arrays.toString(hobby));
return "success";
}
如果想要获取多个同名的参数:
// 1、将形参名和请求参数名保持一致,他会自动将请求参数的值用 "," 拼接起来。此时只包含一个数据。就是拼接完的字符串。
// 2、也可以使用一个 String[] 的形式,将所有的请求参数都保存在数组中。
3、 @RequestParam 注解
如果 请求参数 和 控制器方法形参名不一致时,就可以使用 @RequestParam 注解赋值。
@RequestParam是将请求参数和控制器方法的形参形成映射关系。
@RequestParam 注解有三个属性:
- value : 如果控制器方法的形参名和请求参数的参数名不一致,用 @RequestParam 中的value属性指定 请求参数的参数名。
- required : 是否必须传输次请求参数,默认是true。
- 若设置为true:表示必须传输此注解标识的请求参数,如果不传,且没有设置 default 属性 就会报错:400:Required String parameter 'xxx' is not present
- 若设置为 false,表示不是必须传输 value 所指定的请求参数,如果不传,默认值为 null
- 只有参数名,不传值他是不会报错的。格式:username=&password= 这样不会报错。
- default Value:不管required 是true还是false,只要 value的参数值为空或者不存在时,都会使用默认值为参数赋值。
- username=&password= 也会设置为 default 的值。
<!--测试@RequestParam-->
<form th:action="@{/testRequestParam}">
用户名:<input type="text" name="user_name" ><br>
密码 : <input type="password" name="password"><br>
爱好 : <input type="checkbox" name="hobby" value="smoke">smoke
<input type="checkbox" name="hobby" value="drink">drink
<input type="checkbox" name="hobby" value="tangtou">tangtou
<br>
<input type="submit" value="测试@RequestParam">
</form>
/**
* @RequestParam 注解有三个属性
* value : 设置请求参数的参数名,与形参名形成映射
* required : 是否必须传输此参,true:必须传入 value 所对应的参数,false:不必须传参。默认是true
*defaultValue :参数值为空或者不存在,使用默认值为参数赋值
*
*/
@RequestMapping("/testRequestParam")
public String test_RequestParam(
//当请求参数和形参名不一致时,是获取不到参数值的
//可以使用 @RequestParam 注解,指定参数名(不常用)
@RequestParam(value = "user_name",defaultValue = "jack") String username,
@RequestParam(required = true) String password,
String[] hobby
){
System.out.println("username = " + username +
",password = " + password + ",hobby = " + Arrays.toString(hobby));
return "success";
}
4、@RequestHeader 注解
@RequestHeader 是将 请求头信息 和 控制器方法的形参形成映射关系。
@RequestHeader 也有三个属性:value 、required、defaultValue 。用法和@RequestParam一样。
注意: @RequestHeader 是没有默认映射关系的。想要获取请求头信息必须使用该注解。@RequestParam 是由默认映射关系的,获取请求参数时,使不使用 @RequestParam 都能获取到参数
5、@CookieValue 注解
@CookieValue 注解是 将 cookie信息 和 控制器方法的新参形成映射关系。
@CookieValue 注解也有三个属性: value 、required、defaultValue 。用法和@RequestParam一样。
当浏览器中没有JSESSIONID 时,服务器第一个创建 session 对象,并将 JSESSIONID 响应给浏览器。浏览器将 JSESSIONID 为 key 值,session 对象为 value值保存到 Cookie中。
6、通过 pojo 获取请求参数
创建一个实体类,属性名 要和 请求参数名 保持一致,并且实体类中要有set、get 方法前端控制器 DispatcherServlet 会自动收集参数,并赋值给 实体类。
<!--测试实体类传参。-->
<form th:action="@{/testpojo}" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
性别:<input type="radio" name="sex" value="男">男<input type="radio" name="sex" value="女">女<br>
年龄:<input type="text" name="age"><br>
邮箱:<input type="text" name="email"><br>
<input type="submit">
</form>
//使用一个pojo实体类获取请求参数
//但是要保证 pojo 实体类的属性名 和 请求参数 的属性名保持一致。
@RequestMapping("/testpojo")
public String test_pojo(User user){
System.out.println(user);
return "success";
}
7、解决请求参数乱码问题
在我们使用 实体类获取参数时,会发现有乱码问题:这是因为在发送的请求是 post 请求,需要我们自己设置响应编码。GET 请求乱码问题在Tomcat8之后就自动解决了。
思考一下:当我们在 JavaWeb阶段设置 请求乱码或者响应乱码是不是在BaseServlet中设置。意思是在 发送请求前就设置编码形式。那么就可以使用 过滤器。服务器每次发送请求前都会经过过滤器。
在 web.xml 文件中配置过滤器:
<!--配置过滤器,解决请求参数乱码问题-->
<filter>
<filter-name>filter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<!--设置请求编码-->
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<!--设置响应编码-->
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
五、域对象共享数据
request域:请求域对象。再同一次请求中能共享数据
application域:ServletContext 域对象,在服务器启动时创建,服务器关闭时销毁。
session域:与服务器没有关系,只与浏览器有关系,打开浏览器----关闭浏览器。
pageContext域:只在当前页面能共享数据。
1、使用ServletAPI 共享 request域中的数据
<a th:href="@{/testRequestByServletAPI}">通过servletAPI获取request域中获取数据</a>
使用 thymeleaf 获取数据,可能会爆红,但是不影响是使用。
//使用原生 HttpServletRequest 往 request域中存放数据。
@RequestMapping("/testRequestByServletAPI")
public String testRequestByServletAPI(HttpServletRequest request){
//往请求域中存放数据
request.setAttribute("testRequestScope","hello,servletAPI");
return "success"; //这个就相当于转发
}
2、使用ModelAndView向request域对象共享数据
//使用 ModelAndView 共享request域数据。ModelAndView必须作为返回值返回
//告诉控制器该方法中有一个 ModelAndView对象。
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView(){
ModelAndView mav = new ModelAndView();
/*
ModelAndView 有俩个功能:
Model:负责 request域中共享数据
View: 主要用于视图设置,实现页面跳转。
*/
//往request 域中存放数据 和 request.setAttribute()一样,
mav.addObject("testRequestScope","hello,ModelAndView");
//设置视图名称,实现页面跳转。
mav.setViewName("success");
return mav ;
}
3、使用Model向request域对象共享数据
//使用 Model 共享request域数据。
@RequestMapping("/testModel")
public String testModel(Model model){
model.addAttribute("testRequestScope","hello,Model");
return "success";
}
4、使用map向request域对象共享数据
//使用 map 集合,往map集合中存放值,就相当于往request域中存放值。
//map的key相当于request中的key,value相当于request中的value
@RequestMapping("/testMap")
public String testMap(Map<String,Object> map){
map.put("testRequestScope","hello,Map");
return "success";
}
5、使用ModelMap向request域对象共享数据
@RequestMapping("/testModelMap")
public String testModelMap(ModelMap modelMap){
modelMap.addAttribute("testRequestScope","hello,ModelMap");
return "success";
}
6、Model、ModelMap、Map 之前的关系
Model 、Map 都是一个接口,ModelMap是一个类,那么他们的关系是什么?
我们可以通过反射机制获取他们的全类名。
他们三个的全类名其实都是一样,由此可见他们:
public interface Model{}
public class ModelMap extends LinkedHashMap<String, Object> {}
public class ExtendedModelMap extends ModelMap implements Model {}
public class BindingAwareModelMap extends ExtendedModelMap {}本质上都是一样的。不管使用哪种方式,最终都会被封装为 ModelAndView 对象中。然后返回 ModelAndView 对象。
7、使用 ServletAPI 向 session 共享数据
<a th:href="@{/testSession}">通过ServletAPI集合获取session域中获取数据</a>
@RequestMapping("/testSession")
public String testSession(HttpSession session){
session.setAttribute("testSessionScope","hello,Session");
return "success";
}
8、使用 ServletAPI 向 application 共享数据
<a th:href="@{/testSession}">通过ServletAPI集合获取session域中获取数据</a>
//ServletContext 域共享数据
@RequestMapping("/testApplication")
public String testApplication(HttpSession session){
ServletContext context = session.getServletContext();
context.setAttribute("testApplicationScope","hello,application");
return "success";
}
六、SpringMVC 的视图
1、ThymeleafView
如果使用的是 Thymeleaf 视图解析器:
当控制器方法设置的视图名称没有 前缀 时,他会被 Thymeleaf 解析器解析。在 ThymeleafView 中我们设置了前缀和后缀,加上视图名称,通过转发跳转到某个页面。
如果使用的不是 Thymeleaf ,而是在 jsp 中,无论带不带前缀他都是 InternalResourceView
@RequestMapping("/testThymeleafView")
public String testThymeleafView(){
//只要不带任何的前缀,就会被视图解析器 thymeleaf 解析成 thymeleafView
return "success";
}
他的 viewName 就是在控制器方法中的视图名称。
2、InternalResourceView
SpringMVC 中的转发视图:InternalResourceView
在控制器方法中视图名称以:"forward:" 为前缀时,会创建一个 InternalResourceView 视图。他不会被 Thymeleaf 解析器解析,而是将 " forward :" 前缀去掉,剩下的部分通过最终路径的方式完成跳转。
<a th:href="@{/testForward}">测试InternalResourceView视图</a><br>
@RequestMapping("/testForward")
public String testForward(){
//转发视图,无法跳到具体的某一个页面。
//只能转发到控制器方法的某一个 RequestMapping 映射的某一个路径中。
// return "forward:/success"; 这是错误的
return "forward:/testThymeleafView";
}
转发一次请求,通过链接中的路径,被前端控制器解析,找到控制器对应的方法,根据视图名称加上前缀后缀,最终实现跳转。
注意:
由于我们HTML页面都在WEB-INF的templates目录下,所以使用 forward/Redirect 是无法跳转到具体的某个页面的,只能通过转发到 控制器中某一个方法的RequestMapping 匹配的请求路径
3、 RedirectView
重定向视图:RedirectView
当控制器方法中所设置的视图名称以"redirect:"为前缀时,创建RedirectView视图,此时的视图名称不会被SpringMVC配置文件中所配置的视图解析器解析,而是会将前缀"redirect:"去掉,剩余部分作为最终路径通过重定向的方式实现跳转
<a th:href="@{/testRedirect}">测试RedirectView视图</a><br>
//RedirectView视图
@RequestMapping("testRedirect")
public String testRedirect(){
//因为在WEB-INF/templates 目录下都是需要被解析器解析的
// 无法跳转到具体的某个页面。
return "redirect:/testThymeleafView";
}
重定向是俩次请求,第一次请求通过链接被前端控制器解析,然后 Redirect 重定向再次向浏览器发送 /testThymeleafView 路径,浏览器第二次发送请求。
4、视图控制器 view-controller
如果控制器方法中没有处理请求的过程,只需要实现页面跳转,可以使用 <mvc:view-controller> 标签设置视图名称,实现跳转。
path:匹配符合跳转的路径
view-name:视图名称
<!--如果控制器方法中没有任何的请求处理过程,只需要实现跳转,就可以使用 view-controller 标签实现-->
<mvc:view-controller path="/" view-name="index"></mvc:view-controller>
<!--由于设置了 view-controller 控制器中的请求映射全部都会失效
还需要设置一个 MVC 注解扫描器
-->
<mvc:annotation-driven />
<mvc:view-controller path="/test_view" view-name="test_view"></mvc:view-controller>
当使用标签设置视图名称之后,控制器方法中的映射关系全部都会失效。是需要再开启MVC注解扫描: <mvc:annotation-driven />
5、配置 jsp 视图解析器
jsp 视图解析器 使用的就是:InternalResourceViewResolver
jsp 中创建的视图就有俩种:
一种使用 :forward 前缀创建:InternalResourceView视图
一种使用: redirect 前缀创建:RedirectView 视图。
springMVC.xml 文件:
<!--在 jsp 中使用的是 InternalResourceViewResolver 视图解析器-->
<!--配置视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--配置前缀 /WEB-INF/templates/ -->
<property name="prefix" value="/WEB-INF/templates/"></property>
<!--配置后缀是 .jsp-->
<property name="suffix" value=".jsp"></property>
</bean>
<a href="${pageContext.request.contextPath}/success">访问 success.jsp 页面</a>
@Controller
public class JSPController {
@RequestMapping("/success")
public String success(){
return "success";
}
注意:
如果在访问页面的时候,发现 解析不出来EL表达式,并且地址栏上出现这样的乱码:
$%7BrequestScope.path%7D
这是因为 jsp子啊 2.4版本之前不支持EL表达式,需要设置 page 标签:
<%--由于 jsp 2.4 版本之前是不解析EL表达式的。--%> <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
七、RESUTFul 简介
1、什么是RESTFul
REST:指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是RESTful。
2、RESTFul的特性
资源(Resources):网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的存在。可以用一个URI(统一资源定位符)指向它,每种资源对应一个特性的URI。要获取这个资源,访问它的URI就可以,因此URI即为每一个资源的独一无二的识别符。简单来说,万物皆资源
表现层(Representation):把资源具体呈现出来的形式,叫做它的表现层(Representation)。比如,文本可以用txt格式表现,也可以用HTML格式、XML格式、JSON格式表现,甚至可以采用二进制格式。
状态转换(State Transfer):每发出一个请求,就代表了客户端和服务器的一次交互过程。HTTP协议,是一个无状态协议,即所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生“状态转换”(State Transfer)。而这种转换是建立在表现层之上的,所以就是“表现层状态转换”。具体说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。他们分别对应四种基本操作:GET用来获取资源,POST用来新建资源,PUT用来更新资源,DELETE用来删除资源。
3、RESTful 的实现
REST 风格提倡 URL 地址使用统一的风格设计,从前到后各个单词使用斜杠分开,不使用问号键值对方式携带请求参数,而是将要发送给服务器的数据作为 URL 地址的一部分,以保证整体风格的一致性。
操作 | 传统方式 | REST风格 |
---|---|---|
查询操作 | getUserById?id=1 | user/1-->get请求方式 |
保存操作 | saveUser | user-->post请求方式 |
删除操作 | deleteUser?id=1 | user/1-->delete请求方式 |
更新操作 | updateUser | user-->put请求方式 |
4、HiddenHttpMethodFilter 过滤器
由于 我们普遍发送请求的方式只支持 POST 或者 GET 方式,那么怎么获取 PUT、DELETE 请求呢?
使用 springMVC 提供的 HiddenHttpMethodFilter 过滤器。使用 Ajax 其实也可以,但是Ajax请求只在部分浏览器中支持:
只要使用过滤器就需要在 web.xml 文件中进行注册:
<!--设置 HiddenHttpMethodFilter 过滤器-->
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
获取 PUT 、DELETE 源码解析:
注:
目前为止,SpringMVC中提供了两个过滤器:CharacterEncodingFilter 和HiddenHttpMethodFilter
在web.xml中注册时,必须先注册CharacterEncodingFilter,再注册HiddenHttpMethodFilter
原因:
在 CharacterEncodingFilter 中通过 request.setCharacterEncoding(encoding) 方法设置字符集的
request.setCharacterEncoding(encoding) 方法要求前面不能有任何获取请求参数的操作
而 HiddenHttpMethodFilter 恰恰有一个获取请求方式的操作:
String paramValue = request.getParameter(this.methodParam);
5、使用 RESTFul 风格 实现CURD 操作
(1)准备工作
准备数据库:
CREATE TABLE `t_user` (
`id` int(20) NOT NULL AUTO_INCREMENT,
`userName` varchar(255) DEFAULT NULL,
`passwd` varchar(255) DEFAULT NULL,
`realName` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8
《1》新建基于maven的web模块
《2》在 web.xml 文件中配置 HiddenHttpMethodFilter 、 CharacterEncodingFilter 过滤器、DispatcherServlet 前端控制器。一定要先配置 CharacterEncodingFilter。
《3》配置 springMVC.xml 文件,开启组件扫描 以及 thymeleaf 视图解析器,以及 <mvc-controller> 实现首页跳转
《4》在 WEB-INF 目录下创建 templates 目录 并 创建 首页
《5》使用 Spring5中的 JDBCTemplate 工具类 实现数据库的增删改查。
springMVC.xml 文件配置 数据库连接池以及 JDBCTemplate
<!--创建数据库连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/test"></property>
<property name="username" value="root" ></property>
<property name="password" value="root"></property>
</bean>
<!--注册 JdbcTemplate 操作数据库-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
UserDao:
public interface UserDao {
//增
int add(User user);
int del(Integer id);
int update(User user);
User queryByID(Integer id);
List<User> queryForList();
}
UserDaoImpl:
@Repository
public class UserDaoImpl implements UserDao {
@Autowired //根据数据类型注入对象属性
private JdbcTemplate jdbcTemplate ;
@Override
public int add(User user) {
String sql = "insert into t_user(userName,passwd,realName) values(?,?,?)";
return jdbcTemplate.update(sql,user.getUserName(),user.getpasswd(),user.getRealName());
}
@Override
public int del(Integer id) {
String sql = "delete from t_user where id=?";
return jdbcTemplate.update(sql,id);
}
@Override
public int update(User user) {
String sql = "update t_user set userName=?,passwd=?,realName=? where id=?";
return jdbcTemplate.update(sql,user.getUserName(),user.getpasswd(),
user.getRealName(),user.getId());
}
@Override
public User queryByID(Integer id) {
String sql = "select * from t_user where id =?";
return jdbcTemplate.queryForObject(sql,new BeanPropertyRowMapper<User>(User.class),id);
}
@Override
public List<User> queryForList() {
String sql = "select * from t_user";
return jdbcTemplate.query(sql,new BeanPropertyRowMapper<User>(User.class));
}
}
(2)实现查询用户列表的功能
思路分析:
1、首先在 Controller 层 注入 dao 层对象。
2、每一个功能应该对应一个方法,创建查询所有用户信息的方法
3、方法里调用 dao 层的 queryForList 方法查询数据库。
4、将查询到的结果保存到 request 域中。并返回一个视图名称,实现跳转。
5、在跳转的页面中,使用 each 循环取出 request域中的数据,显示到页面中。
Controller 层:
@Autowired //根据数据类型自动输入属性
private UserDao userDao ;
//使用 RESTFul风格 查询所有的用户信息
// @RequestMapping(value = "/findAllUser",method = RequestMethod.GET)
@GetMapping("/user")
public String findAllUser(Model model){
List<User> userList = userDao.queryForList();
//将查询出来的数据保存到 request域中。
model.addAttribute("user",userList);
return "user_list";
}
user_list 页面:
<table align="center">
<tr>
<th>id</th>
<th>userName</th>
<th>password</th>
<th>realName</th>
<th>options</th>
</tr>
<!--thymeleaf 语法 。和 jsp <c:forEach> 功能一样-->
<!--userList:相当于<c:forEach>中的var,${userList}相当于<c:forEach>中的item -->
<tr th:each="user : ${user}">
<td th:text="${user.id}"></td>
<td th:text="${user.userName}"></td>
<td th:text="${user.passwd}"></td>
<td th:text="${user.realName}"></td>
<td>
<a href="">delete</a>
<a href="">update</a>
</td>
</tr>
</table>
(3)实现删除功能
思路分析:
1、在 user_list 页面中的删除链接中,携带 id 参数发送请求到 Controller 层。
2、将 删除的请求设置为DELETE 请求。
设置 DELETE 请求的条件:1、发送的请求为POST请求。2、参数名为 _method 参数值为:DELETE
由于我们的超链接只能发送 GET 请求,所以我们需要用到表单来发送POST请求,并使用 jQuery 设置表单中的 action 属性 实现跳转。
3、在 Controller 层 调用 userDao.del 方法,实现功能
4、返回视图名称,重定向到 查询列表。 redirect : /user
删除完用户信息,已经和上一次请求没有关系了,所以推荐用重定向。
Controller 层:
//删除功能
@DeleteMapping("/user/{id}")
public String delete(@PathVariable("id") Integer id){
userDao.del(id);
return "redirect:/user";
}
删除链接:
<a class="del" th:href="@{'/user/' + ${user.id}}">delete</a>
<!--table标签外创建表单,用于发送DELETE请求。-->
<form action="" method="post">
<input type="hidden" name="_method" value="DELETE">
</form>
jQuery 代码:
在webapp下创建 static 目录,引入 js 文件。
<!--引入js文件,不要忘记加上根路径-->
<script type="text/javascript" th:src="@{static/jquery-3.6.0.js}"></script>
<script type="text/javascript">
$(function () {
// 给删除绑定单击事件
$(".del").click(function () {
if (confirm("确定删除吗?")) {
// 1、设置表单的action的跳转路径为 删除连接中的href
$("form").attr("action", this.href);
$("form").submit(); //2、提交表单
return false;//3、阻止超链接的跳转行为。
}else{
return false ;
}
});
});
</script>
实现删除功能可能出现的问题:
1、在设置超链接中的路径时, @{/delete/ ${user.id} } 这样直接拼接是错误的。@{/delete/} + ${user.id} 可以使用这样的形式进行拼接起来。也可以使用代码块中的格式。
2、在引入jQuery文件时,如果发现 无论怎么修改 js 文件路径,始终提示没有找到报404,说明 js 文件的路径被SpringMVC解析,一旦每SpringMVC解析当然找不到 js 文件,因为js文件是一个静态资源。所以我们需要在 springMVC.xml 配置文件中开启静态资源访问。
当配置这个之后,静态资源还是会交给 SpringMVC 处理,但是当它找不到匹配的请求的路径时,他会交给 default-servlet 处理,最终找到静态资源,当然如果这个资源不存在还是会报 404 。
<mvc:default-servlet-handler />
还有一种可能:如果你的 target 目录并没有把 js 文件打包,需要我们需要重新打包一下。
![]()
![]()
(4)实现增加功能
思路分析:
1、在 user_list 页面增加跳转到增加页面的超链接。
2、由于不需要处理其他业务请求,所以直接在springMVC.xml 文件中增加<mvc-controller>标签实现跳转
3、在 增加页面 填写数据之后,点击 提交按钮,向 controller 提交请求参数。
4、在 controller 层接受参数,并调用 userdao.add 方法 对数据库增加数据。
5、重定向到 redirect: / user 查询用户列表 。不要使用转发,因为会存在刷新问题。
user_list 页面:
<!--跳转到增加页面,不需要处理其他业务,所以可以配置<mvc-controller>直接实现跳转-->
<th>options(<a th:href="@{/add}">add</a>)</th>
add 页面:
<!--增加用户功能,发送 POST 请求-->
<form th:action="@{/user}" method="post">
userName:<input type="text" name="userName"><br>
passwd :<input type="text" name="passwd"><br>
realName:<input type="text" name="realName"><br>
<input type="submit" value="增加">
</form>
springMVC.xml 文件:
<!--跳转到增加页面-->
<mvc:view-controller path="add" view-name="add"></mvc:view-controller>
controller 层:
//增加用户
@PostMapping("/user")
public String addUser(User user){
userDao.add(user);
//重定向到 查询列表功能
return "redirect:/user";
}
(5)实现修改功能
思路分析:
1、在 user_list 页面增加修改的超链接,并传输 id 参数,发送请求到 controller 层。
2、在 controller 层的queryByID方法中接收 参数,并调用dao层 userDao.queryByID 查询用户信息,保存在 request域中。
3、设置视图名称,跳转到修改页面。
4、在修改页面,获取 request域中的数据,回显到文本框中。
5、设置俩个隐藏域,一个隐藏域传输id参数,一个隐藏域传输PUT请求。
6、在controller 层中的update方法,调用 userDao.update 方法实现修改用户信息。
user_list 页面:
<!--修改用户信息-->
<a th:href="@{'/user/' + ${user.id}}" class="update">update</a>
controller :
//根据id查询用户信息
@GetMapping("/user/{id}")
public String queryByID(@PathVariable Integer id ,Model model){
//根据id查询信息
User user = userDao.queryByID(id);
//将 用户信息存放到 请求域中
model.addAttribute("user",user);
return "update";
}
//修改用户信息
@PutMapping("/user")
public String update(User user){
userDao.update(user);
return "redirect:/user";
}
update 页面:
<h1>修改用户</h1>
<!--修改用户功能,发送 POST 请求-->
<form th:action="@{/user}" method="post">
<input type="hidden" name="id" th:value="${user.id}">
<!--修改功能发送PUT请求-->
<input type="hidden" name="_method" value="PUT">
userName:<input type="text" name="userName" th:value="${user.userName}"><br>
passwd :<input type="text" name="passwd" th:value="${user.passwd}"><br>
realName:<input type="text" name="realName" th:value="${user.realName}"><br>
<input type="submit" value="update">
</form>
(6)关于 SpringMVC 处理静态资源的过程
上面我们说,如果我们不开放静态资源的访问,SpringMVC 就会处理访问静态资源的请求。
其实我们可以看看Tomcat中的web.xml 文件,其实在Tomcat 中 web.xml 文件配置了default-servlet的匹配路径,那么Tomcat中的web.xml 和我们工程中的 web.xml 文件是什么关系呢?
是继承关系,以工程中的 web.xml 文件优先,由于我们在工程中的 web.xml 文件配置了 前端控制器,所以他会优先交给 SpringMVC 中的前端控制器处理,因此会找不到静态资源。如果我们在 工程中的web.xml 文件中配置了 default-Servlet,前端控制器找不到就会交给 default-servlet 处理。
这俩个配置要配合使用:
<!--配置 mvc-controller 会影响其他映射关系的跳转--> <!--需要配置mvc:annotation-driven --> <mvc:annotation-driven></mvc:annotation-driven> <!--开放对静态资源的访问 --> <mvc:default-servlet-handler />
八、HttpMessageConverter
HttpMessageConverter:报文信息转换器,将请求报文转换为Java对象,或将Java对象转换为响应报文
HttpMessageConverter 提供了两个注解和两个类型:@RequestBody,@ResponseBody,RequestEntity,ResponseEntity 俩个关于请求报文的,俩个关于响应报文的。
1、@RequestBody
@RequestBody:获取请求体,转换成 JAVA 对象。需要将控制器方法中设置一个形参,将请求体赋值给形参。
只有发送POST 请求时,请求参数才会在请求体中。
<form th:action="@{/testRequestBody}" method="post">
username:<input type="text" name="username">
password:<input type="password" name="password" >
<input type="submit" value="提交">
</form>
@RequestMapping("/testRequestBody")
public String testRequestBody(@RequestBody String body){
System.out.println(body); //username=admin&password=123
return "success";
}
2、RequestEntity
RequestEntity 封装请求报文的一种类型,需要在控制器方法的形参中设置该类型的形参,当前请求的请求报文就会赋值给该形参,可以通过getHeaders()获取请求头信息,通过getBody()获取请求体信息
<form th:action="@{/testRequestEntity}" method="post">
username:<input type="text" name="username"><br>
password:<input type="password" name="password" ><br>
<input type="submit" value="测试RequestEntity">
</form>
@RequestMapping("/testRequestEntity")
public String testRequestEntity(RequestEntity<String> requestEntity){
System.out.println(requestEntity.getHeaders()); //获取请求头
System.out.println(requestEntity.getBody()); //获取请求体
return "success";
}
3、@ResponseBody
@ResponseBody 用于标识一个控制器方法,可以将该方法的返回值直接作为响应报文的响应体响应到浏览器
<a th:href="@{/testResponseBody}">使用@ResponseBody响应浏览器</a>
@RequestMapping("/testResponseBody")
@ResponseBody
public String testResponseBody(){
//不加 @ResponseBody 是视图名称,实现跳转
//加上 @ResponseBody 将 success作为响应体响应给浏览器
return "success";
}
4、springMVC 处理 json步骤
1、导入 jackson 依赖
<!--json依赖-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.4</version>
</dependency>
2、在SpringMVC的核心配置文件中开启mvc的注解驱动,此时在HandlerAdaptor中会自动装配一个消息转换器:MappingJackson2HttpMessageConverter,可以将响应到浏览器的Java对象转换为Json格式的字符串
<mvc:annotation-driven />
这段配置到目前有三个作用:
1、设置<mvc-controller>时,能够匹配其他映射路径
2、配合开启访问静态资源使用
3、转换 json 字符串时使用。
3、 在处理器方法上使用@ResponseBody注解进行标识
// 使用 @ResponseBody 返回对象
@RequestMapping("/testResponseUser")
@ResponseBody
public User testResponseUser(){
return new User(1,"rose");
}
4、将Java对象直接作为控制器方法的返回值返回,就会自动转换为 Json 格式的字符串
注意:是转换为 json 格式的字符串,浏览器中只能接受字符串,并不是 json 对象。json 对象只在 JavaScript 中存在。在 java 中的是Java字符串。
{"id":1,"name":"rose"}
5、SpringMVC 处理 Ajax 请求步骤
超链接请求:
<a th:href="@{/testResponseAjax}" id="ajax">测试 Ajax 请求</a>
Ajax 请求:
<script type="text/javascript" th:src="@{/static/js/jquery-3.6.0.js}"></script>
<script type="text/javascript">
$(function () {
$("#ajax").click(function(){
$.ajax({
//超链接的 href
url:this.href,
//请求方式
type:"post",
//请求参数
data:"username=zhangsan&password=123",
//data:将controller响应的数据赋值给data
success:function(data){
alert(data);
}
});
return false ;
});
});
</script>
controller:
// 使用 @ResponseBody 返回对象
@RequestMapping("/testResponseAjax")
@ResponseBody
public String testResponseAjax(String username, String password){
System.out.println("username = " +username + "password = "+ password);
return "hello ajax";
}
6、@RestController 注解
@RestController 是一个复合注解,标识在控制器的类上,相当于为类增加 @Controller 注解,并且为类中的所有方法都加上了 @ResponseBody注解。
7、ResponseEntity
ResponseEntity用于控制器方法的返回值类型,该控制器方法的返回值就是响应到浏览器的响应报文。多用于文件下载。
8、文件下载
<a th:href="@{/testDown}">下载图片</a>
@RequestMapping("/testDown")
public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws IOException {
//获取ServletContext对象
ServletContext servletContext = session.getServletContext();
//获取服务器中文件的真实路径
String realPath = servletContext.getRealPath("/static/img/1.jpg");
//创建输入流
InputStream is = new FileInputStream(realPath);
//创建字节数组
byte[] bytes = new byte[is.available()];
//将流读到字节数组中
is.read(bytes);
//创建HttpHeaders对象设置响应头信息
MultiValueMap<String, String> headers = new HttpHeaders();
//设置要下载方式以及下载文件的名字
headers.add("Content-Disposition", "attachment;filename=1.jpg");
//设置响应状态码
HttpStatus statusCode = HttpStatus.OK;
//创建ResponseEntity对象
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, headers, statusCode);
//关闭输入流
is.close();
return responseEntity;
}
9、文件上传
(1)创建表单
<!--上传文件必须是post请求
enctype="multipart/form-data 以二进制流的方式传输
-->
<form th:action="@{/testUp}" method="post" enctype="multipart/form-data">
<input type="file" name="photo"><br>
<input type="submit" value="上传">
</form>
(2)配置文件上传服务器,id不要错,因为 SpringMVC 中是根据id注入对象的。
<!--配置文件上传解析器,id不能错,必须这个名字。-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
</bean>
(3)controller 层:
@RequestMapping("/testUp")
//参数名要与表单中的name一致。
public String testUp(MultipartFile photo,HttpSession session) throws IOException {
// 获取 file 类型的属性名。
// System.out.println(photo.getName());
//获取上传的文件名。
String filename = photo.getOriginalFilename();
// System.out.println(photo.getOriginalFilename());
ServletContext context = session.getServletContext();
//1、获取文件的真实路径
String photoPath = context.getRealPath("photo");
System.out.println(photoPath);
//2、通过给定的 路径 创建File实例对象。
File file = new File(photoPath);
if (!file.exists()){
//3、判断以photoPath为路径名的文件或目录是否存在,不存在则创建对应的目录
file.mkdir();
}
// 4、拼接最终的路径,File.separator 自动分隔符。
String finalPath = photoPath + File.separator + filename ;
//5、将文件上传到指定位置。只能用一次。用完自动关闭。
photo.transferTo(new File(finalPath));
return "success" ;
}
10、解决文件名重复问题
如果我们上传的文件是重复的,那么他之前的文件是会被删除的,这样是错误的。
那么,怎么才能让文件名不重复呢?
在 java.uitl 有一个 UUID 类,他可以自动随机生成32位不同的数字。我们可以使用 UUID 加文件后缀的方法生成文件名。
//防止文件名重复问题
//对"."进行截取。得到文件名的后缀。
//一定要最后一次出现 . 的位置开始截取,因为文件名有可能:1.1.1.jpg
String suffix = filename.substring(filename.lastIndexOf("."));
//使用uuid
String uuid = UUID.randomUUID().toString();
//将uuid和后缀拼接在一起。
filename = uuid + suffix ;
九、拦截器
拦截器是对控制器中的方法进行拦截
拦截器中的三个方法:
1、配置拦截器
(1)创建一个普通的类,实现 HandlerInterceptor 接口,并重写 preHandle,postHandle,afterCompletion 三个方法。
在拦截器 中的 preHandle 方法,是对控制器方法进行拦截处理的。我们可以看看DispatcherServlet源码:
通过源码也可以看出来,当 preHandle 中的返回值为 false 时,才会对请求进行拦截,返回值为true,放行。
(2)在springMVC.xml 文件中配置拦截器。
<!--配置拦截器-->
<mvc:interceptors>
<!--第一种配置方式:对所有请求都会拦截-->
<!-- <bean class="interceptors.FirstInterceptors"></bean>-->
<!--第二种配置方式:需要在拦截类加上注解,创建对象。
和第一种配置方式一样,对所有请求都会拦截-->
<!--<ref bean="firstInterceptors"></ref>-->
<!--第三种配置方式:-->
<mvc:interceptor>
<!--/* 不是拦截所有请求,/** 是拦截所有请求-->
<mvc:mapping path="/*"/>
<!--<mvc:mapping path=""/> 可以配置多条拦截路径-->
<!--设置不拦截的路径-->
<mvc:exclude-mapping path="/"/>
<ref bean="firstInterceptors"></ref>
</mvc:interceptor>
</mvc:interceptors>
拦截器工作流程:
2、多个拦截器的执行顺序
当多个拦截器中的preHandle返回值都是true时:
preHandle 方法 按照 springMVC 配置文件配置拦截器的顺序执行。
postHandle 和 afterCompletion 按照 springMVC 配置文件配置拦截器的反序执行
为什么会这么执行呢?我们看看源码:
首先我们看 preHandle 方法 执行顺序:
postHandle 方法的执行顺序:
afterCompletion 执行顺序:
当有一个拦截器的返回值是false时:
(1)执行配置它之前拦截器的 preHandle 方法。比如说:我先配置 FirstInterceptor 后配置 SecondInterceptor , 在 SecondInterceptor 的 preHandle 方法中设置返回值false。那么FirstInterceptor 中的preHandle 会执行。
(2)所有的 postHandle 方法都不会执行。
(3)返回值为 false 之前的拦截器的 afterCompletion 方法会执行。也就是 FirstInterceptor 的 afterCompletion 方法会执行。
十、异常处理器
1、使用配置文件处理异常
SpringMVC提供了一个处理控制器方法执行过程中所出现的异常的接口:HandlerExceptionResolver
HandlerExceptionResolver 接口的实现类有:DefaultHandlerExceptionResolver 和SimpleMappingExceptionResolver
SpringMVC提供了自定义的异常处理器 SimpleMappingExceptionResolver,使用方式:
<!--配置异常处理器-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!--算术异常
value就是跳转的页面,符合视图规则。没有前缀被 thymeleaf解析。
-->
<prop key="java.lang.ArithmeticException">error</prop>
</props>
</property>
<!--显示异常信息到错误页面
将错误信息放到请求域中,以value为键,错误信息为值。
-->
<property name="exceptionAttribute" value="error" />
</bean>
错误页面
<p th:text="${error}"></p>
2、使用注解处理异常
@ControllerAdvice // @Controller 增强注解,为Controller增加一些功能。
public class ExceptionController {
@ExceptionHandler(value = {ArithmeticException.class,NullPointerException.class})
public String testException(Exception ex, Model model){
//将错误信息保存在请求域中。
model.addAttribute("error",ex);
return "error";
}
}
十一、注解配置SpringMVC配置文件
创建一个类继承 AbstractAnnotationConfigDispatcherServletInitializer 类。
/*
代替 工程下的 web.xml 文件:
1、配置过滤器
2、配置前端控制器
3、指定springMVC.xml 文件
*/
public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* spring配置类
* @return
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
/**
* SpringMVC配置文件
* @return
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebConfig.class};
}
/**
* 指定DispatcherServlet的映射规则,url-pattern
* @return
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
/**
* 配置过滤器
* @return
*/
@Override
protected Filter[] getServletFilters() {
//编码过滤器
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
characterEncodingFilter.setForceResponseEncoding(true);
//设置转换PUT、DELETE请求过滤器
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
return new Filter[]{characterEncodingFilter,hiddenHttpMethodFilter};
}
}
创建SpringConfig 和 WebConfig 类 :
@Configuration
public class SpringConfig {
}
WebConfig 相当于 springMVC.xml 文件:
/*
代理 springMVC.xml 文件 :
1、开启组件扫描
2、配置视图解析器
3、开启静态资源的访问 <mvc:default-servlet-handler>
4、开启mvc注解 <mvc:annotation-driven>
5、设置 mvc-controller
6、异常处理器
7、配置拦截器
8、配置文件上传解析器
*/
@Configuration
//1、开启组件扫描
@ComponentScan({"controller"})
// 4、开启MVC注解
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
//3、开启静态资源的访问 <mvc:default-servlet-handler>
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
// 5、设置 mvc-controller
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//addViewController :设置请求路径 setViewName:设置视图名称
registry.addViewController("/hello").setViewName("hello");
}
// 7、配置拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
FirstInterceptors first = new FirstInterceptors();
//增加拦截器,addPathPatterns 设置拦截路径。excludePathPatterns:不拦截的路径
registry.addInterceptor(first).addPathPatterns("/**").excludePathPatterns("/");
}
// 8、配置文件上传解析器
@Bean
public MultipartResolver getMultiPartResolver(){
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
return multipartResolver ;
}
// 6、异常处理器
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
//异常处理器
SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
//Properties集合
Properties prop = new Properties();
//key:产生的异常类型,value“:跳转的页面。
prop.setProperty("java.lang.NullPointerException","error");
//映射关系
exceptionResolver.setExceptionMappings(prop);
//将异常信息保存在request域中。键值名:exception
exceptionResolver.setExceptionAttribute("exception");
resolvers.add(exceptionResolver);
}
//配置生成模板解析器
@Bean
public ITemplateResolver templateResolver() {
WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
// ServletContextTemplateResolver需要一个ServletContext作为构造参数,可通过WebApplicationContext 的方法获得
ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(
webApplicationContext.getServletContext());
templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setSuffix(".html");
templateResolver.setCharacterEncoding("UTF-8");
templateResolver.setTemplateMode(TemplateMode.HTML);
return templateResolver;
}
//生成模板引擎并为模板引擎注入模板解析器
@Bean
public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
return templateEngine;
}
//生成视图解析器并未解析器注入模板引擎
@Bean
public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setCharacterEncoding("UTF-8");
viewResolver.setTemplateEngine(templateEngine);
return viewResolver;
}
}
十二、SpringMVC 的执行流程
1、SpringMVC常用组件
-
DispatcherServlet:前端控制器,不需要工程师开发,由框架提供
作用:统一处理请求和响应,整个流程控制的中心,由它调用其它组件处理用户的请求
-
HandlerMapping:处理器映射器,不需要工程师开发,由框架提供
作用:根据请求的url、method等信息查找Handler,即控制器方法.@RequestMapping
-
Handler(Controller):处理器(控制器),需要工程师开发
作用:在DispatcherServlet的控制下Handler对具体的用户请求进行处理
-
HandlerAdapter:处理器适配器,不需要工程师开发,由框架提供
作用:通过HandlerAdapter对处理器(控制器方法)进行执行。在我们DispatcherServlet源码中有一个 mv = ha.handle() 方法,ha 就是:HandlerAdapter。执行控制器方法,返回一个ModelAndView对象。
-
dViewResolver:视图解析器,不需要工程师开发,由框架提供
作用:进行视图解析,得到相应的视图,例如:ThymeleafView、InternalResourceView、RedirectView
-
View:视图
作用:将模型数据通过页面展示给用户
2、SpringMVC 初始化过程
DispatcherServlet 本质上是一个 Servlet,所以天然的遵循 Servlet 的生命周期。所以宏观上是 Servlet 生命周期来进行调度。
我们追踪看一下源码:
3、SpringMVC 处理请求的过程
4、SpringMVC 调用组件执行流程
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
/*
mappedHandler:调用链
包含handler、interceptorList、interceptorIndex
handler:浏览器发送的请求所匹配的控制器方法
interceptorList:处理控制器方法的所有拦截器集合
interceptorIndex:拦截器索引,控制拦截器afterCompletion()的执行
*/
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
// 通过控制器方法创建相应的处理器适配器,调用所对应的控制器方法
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 调用拦截器的preHandle()
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
// 由处理器适配器调用具体的控制器方法,最终获得ModelAndView对象
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
// 调用拦截器的postHandle()
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 后续处理:处理模型数据和渲染视图
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
render方法 渲染视图:
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
// 处理模型数据和渲染视图
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
// Exception (if any) is already handled..
// 调用拦截器的afterCompletion()
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
5、SpringMVC 整体的执行流程
(1)浏览器向服务器发送请求。前端控制器接受请求,并解析请求路径中的URI(统一资源定位符)。
(2)判断URI与控制器中的请求映射是否一致。
若请求映射不存在
a、判断是否配置了 <mvc:default-servlet>(静态资源访问),若没有配置,直接报404错误。
(3)若配置了 <mvc:default-servlet> ,会交给默认的Servlet 处理,如果还是找不到也是会报 404 错误。
若请求映射存在
(4)调用 HandleMapping 获取对应的 Handle(Controller) 对象。这个 Handle 中保存了Handle对象(控制器中的方法)、拦截器、拦截器索引
(5)得到Handle对象之后, 获取HandleAdapter 处理器适配器,用于调用控制器中的方法。
(6)获取 HandleAdapter 之后,会执行拦截器中的 preHandle 方法(正序)
(7)通过 处理器适配器调用控制器中的方法。在执行方法的时候,前端控制器会将请求中的信息赋给控制器方法中的形参中,在赋值的过程中 SpringMVC 会做一些以下事情:
a、HttpMessageConveter :通过请求报文信息转换器,将请求中的信息(如json、xml格式)转换成对应的对象,在响应到浏览器中
b、数据转换:由于我们浏览器只能发送字符串形式的参数,当你的参数不是String类型时,他会自动给你转换。
c、数据格式化:将参数转换成日期、数字等形式
(8)在调用方法完成之后,会返回一个ModelAndView 对象,Model 存放数据,View存放视图名称。
(9)此时开始执行 拦截器中的 PostHandle 方法。
(10)根据返回的 ModelAndView 对象,如果有异常会通过HandleExceptionResolver处理异常,如果没有异常,选择一个适合视图解析器(Thymeleaf,InternalResourceView,RedirectView),进行视图渲染。
(11)此时执行 拦截器中的 afterCompletion 方法。
(12)将渲染结果返回到浏览器中