目录
6. @RequestMapping注解的headers属性
1. 使用ServletAPI向request域对象共享数据
2. 使用ModelAndView向request域对象共享数据
4. CharacterEncodingFilter和HiddenHttpMethodFilter的配置顺序
2. 创建SpringConfig配置类,代替spring的配置文件
3. 创建WebConfig配置类,代替SpringMVC的配置文件
一、SpringMVC简介
1. 什么是MVC

注:三层架构分为表述层(或表示层)、业务逻辑层、数据访问层,表述层表示前台页面和后台Servlet
2. 什么是SpringMVC
SpringMVC是一个基于 MVC模式的轻量级Web框架,是Spring框架的一个模块,和Spring可以直接整合使用。SpringMVC 是 Spring 为表述层开发提供的一整套完备的解决方案。在表述层框架历经 Strust、 WebWork、Strust2 等诸多产品的历代更迭之后,目前业界普遍选择了 SpringMVC 作为 Java EE 项目表述层开发的首选方案。SpringMVC代替了Servlet技术,它通过一套注解,让一个简单的Java类成为处理请求的控制器,而无须实现任何接口。
3. SpringMVC的特点
如,相对于原生Servlet,每个请求都要被我们自己定义的Servlet进行处理,而对于SpringMVC,因为当前所有请求都要被前端控制器
二、SpringMVC入门案例
1. 开发环境
2. 创建maven工程
2.1. 通过idea的模板创建
说明:如果是手动创建的maven工程(没有勾选Create from archetype),需要自行添加web模块:首先要创建webapp目录,创建完webapp目录后,还需要添加一个web.xml,web.xml作为web工程的描述符,是web工程的一个入口配置文件,每当启动Tomcat服务器,首先要加载的文件就是web.xml,这个配置文件里面可以注册Servlet、过滤器、监听器。
2.2. 打包方式为war
2.3. 引入依赖
<!--当前web工程打成war包之后,这里所依赖的jar包,在打成war包之后都会放到WEB-INF下面的lib中-->
<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 -->
<!-- provided:表示已被提供,被服务器提供。就是当前服务器中已经提供了Servlet的jar包,在这里把依赖范围设置成为provided之后,当我们把当前的web工程打成为war包之后,
那这个Servlet的jar包就不会存在于当前工程打成war包之后的WEB-INF下面的lib中
-->
<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>
</dependencies>
<!--
scope是用来限制 dependency 的作用范围的,影响maven项目在各个生命周期时导入的package的状态,主要管理依赖的部署。
scope 的作用范围:
(1)compile:默认值,适用于所有阶段(表明该jar包在编译、运行以及测试中路径均可见),并且会随着项目一起发布。
(2)test:只在测试时使用,用于编译和运行测试代码,不会随项目发布。
(3)runtime:无需参与项目的编译,不过后期的测试和运行周期需要其参与,与 compile相比,跳过了编译。如 JDBC 驱动,适用运行和测试阶段。
(4)provided:编译和测试时有效,但是该依赖在运行时由服务器提供,并且打包时也不会被包含进去,如 servlet-api。
(5)system:类似 provided,需要显式提供包含依赖的jar,不会从 maven仓库下载,而是从本地文件系统获取,需要添加systemPath的属性来定义路径。
-->

3. 配置web.xml
<!-- 配置SpringMVC的前端控制器,对浏览器发送的请求统一进行处理 -->
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servletclass>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<!--
设置SpringMVC的前端控制器所能处理请求的请求路径,"/"所匹配的请求可以是/testServlet、/login.do或后缀为.html或.js或.css方式的请求路径,
但是"/"不能匹配.jsp请求路径的请求
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 配置SpringMVC的前端控制器,对浏览器发送的请求进行统一的处理,<servlet>和<servlet-mapping>这两个标签是来共同注册一个Servlet-->
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--初始化参数,指定SpringMVC配置文件的位置和名称-->
<init-param>
<!--显式上下文配置位置,参数名contextConfigLocation为固定值,需要有一个固定的值来让SpringMVC知道,
这是表示当前配置文件的位置和名称,所以这里使用的参数名,一定是由SpringMVC在它的前端控制器DispatcherServlet中已经定义好的-->
<param-name>contextConfigLocation</param-name>
<!-- 使用classpath:表示从类路径查找配置文件,也就是target中的classes目录下-->
<param-value>classpath:SpringMVC.xml</param-value>
</init-param>
<!--
作为框架的核心组件,在启动过程中有大量的初始化操作要做,而这些操作放在第一次请求时才执行会严重影响访问速度
因此需要通过此标签将前端控制器DispatcherServlet的初始化时间提前到服务器启动时
-->
<load-on-startup>1</load-on-startup>
</servlet>
<!--设置SpringMVC的核心控制器所能处理请求的请求路径,既然是对浏览器发送的请求进行统一的处理,<url-pattern>匹配路径就不能是固定、具体的路径-->
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
<!-- <url-pattern>标签中使用"/"和"/*"的区别:
"/"所匹配的请求可以是/testServlet、/login.do或后缀为.html或.js或.css方式的请求路径,但是"/"不能匹配.jsp请求路径的请求。
因为jsp本质就是一个Servlet,它是需要经过当前服务器中指定的Servlet(就是它自己)来进行处理,假设"/"能够匹配.jsp,
那么.jsp后缀路径的请求也会被SpringMVC的前端控制器来处理,就会把当前的jsp请求当做是一个普通的请求来处理,而不会找到它相对应的jsp页面。
因此,"/"可以避免在访问jsp页面时,该请求被DispatcherServlet处理,从而找不到相应的页面.
"/*"可以匹配所有请求包括.jsp请求。例如在使用过滤器时,若需要对所有请求进行过滤,就需要使用/*的写法
-->
我们都知道,web.xml中主要就是来注册Servlet、过滤器、监听器,那为什么使用SpringMVC的时候还需要配置web.xml呢?
因为SpringMVC是基于原生的Servlet,它为我们提供了一个封装处理请求过程的前端控制器DispatcherServlet,Servlet要想处理请求,必须在web.xml中注册。
可以这么理解,SpringMVC对Servlet进行了封装,其实本质就是一个Servlet,封装了Servlet之后就有了一个功能非常强大的前端控制器DispatcherServlet。原先浏览器发送的请求需要我们自己去写Servlet来处理,而对于SpringMVC,浏览器发送的请求都会被SpringMCV提供的前端控制器DispatcherServlet进行统一的处理。这个时候,它就会将请求和响应中很多的一些过程(执行步骤)进行封装,例如,获取请求参数、向域对象设置值、实现页面跳转、转发和重定向的时候,这个时候,DispatcherServlet就会对这些操作进行统一处理。
注册的原因:服务器上的每一项资源,都有一个路径与之对应,而浏览器不能直接访问一个类,因为并不是所有的类都能够接受请求和响应。JavaEE平台下的技术标准提供了Servlet,可以通过访问Servlet来实现,要想访问Servlet,必须要给它设置一个匹配路径,每当我们所访问的路径与我们当前所设置路径匹配的时候,那这个时候当前的请求就会被我们的Servlet来进行处理,所以我们要配置web.xml。
4. 创建SpringMVC的配置文件
编写SpringMVC核心配置文件springMVC.xml,该文件和Spring配置文件写法一样。
<!--扫描组件-->
<context:component-scan base-package="com.cgc.mvc.controller"/>
<!-- 配置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">
<!--thymeleaf在进行解析的时候,必须要设置一个视图前缀和视图后缀-->
<!-- 视图前缀 -->
<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>
<!--
视图是要被视图解析器解析的,把我们设置的视图名称加上前缀和后缀,最终就能跳转到我们指定的页面
每当我们实现页面跳转的时候,如果我们指定的视图名称符合条件的话,它就会被我们当前的视图解析器解析,找到相对应的页面,实现页面跳转
-->
说明:框架简单的说,其实就是配置文件+jar包,引入依赖之后,jar包有了,要想实现相对应功能,还需要去创建SpringMVC的配置文件
5. 创建请求控制器
@Controller
public class DemoController {
}
6. 测试Demo工程
@RequestMapping注解,处理请求和控制器方法之间的映射关系
@RequestMapping注解的value属性可以通过请求地址匹配请求
/**
* @RequestMapping注解:处理请求和控制器方法之间的映射关系
* 当客户端发送的请求和@RequestMapping注解的value值匹配的时候,就会执行当前注解所标识的方法
* "/"表示匹配的是当前工程的应用上下文路径:localhost:8080/SpringMVC/
* "/"--> 跳转到/WEB-INF/templates/index.html
*/
@RequestMapping("/")
public String index() {
//返回视图名称,被视图解析器进行解析
//每当实现页面跳转的时候,如果视图名称符合条件的话,它就会被当前的视图解析器解析, 找到相对应的页面,实现页面跳转
return "index";
}
<!DOCTYPE html>
<!--xmlns:th="http://www.thymeleaf.org" 是thymeleaf的命名空间,有了它页面中才能使用thymeleaf的语法-->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>首页</h1>
<a th:href="@{/toTarget}">访问目标页面welcome.html</a><br/>
</body>
</html>
<!--当前页面是在WEB-INF下的,因为WEB-INF下的内容是受保护的,所以浏览器是不能直接访问的,通过重定向也是不能访问的,只能通过请求转发来进行访问-->
在请求控制器中创建处理请求的方法
@RequestMapping("/toTarget")
public String toWelcome() {
return "welcome";
}
关于绝对路径的说明:
以"/"开头的路径叫做绝对路径,而绝对路径又分为浏览器解析的和服务器解析的,浏览器解析的绝对路径会把"/"解析成:localhost:8080/
超链接中的绝对路径就是由浏览器解析的,这个时候的"/"表示的不是从应用上下文路径下访问,而是从项目的部署目录开始访问(Servlet容器:Tomcat的webapps目录下开始找,即从localhost:8080下访问)。
为什么要用thymeleaf呢?
<a href="/toTarget"> 直接这样写,浏览器所解析的绝对路径会少一个上下文路径(点击‘Edit Configurations’打开"Run/Debug Configurations",这个时候我们选择deployment,就会看到Application context),thymeleaf用@{}包起来的绝对路径,会自动添加上下文路径。使用thymeleaf,想要通过thymeleaf解析某个属性,就在属性前面加 th:,当检测到@{/toTarget}中使用的是绝对路径,就会自动帮助我们添加上下文路径。
关于Application context
localhost:8080/projectName或者是localhost:8080就是我们平常所说的应用上下文,项目中的路径名都是相对于这个应用上下文来说的。
在idea下开发的时候,有时候我们可能需要让访问路径带上项目名,但是,idea默认是为每个项目单独配置tomcat的,要想让idea的应用上下文改变,只需将这个Application context改成你需要的项目名就行了。
7. 总结
三、@RequestMapping注解
1. @RequestMapping注解的功能
2. @RequestMapping注解的位置
/**
* @RequestMapping可以加在类上,给不同模块的控制器设置@RequestMapping,主要用于区分不同业务与层次的模块,实现路径识别与管理
* 如果不加,有可能在不同的控制器中出现相同的请求映射,即有多个方法上面的@RequestMapping,配了相同的请求地址
*
* 说明:在所有的控制器中,@RequestMapping所能匹配到的请求地址必须是唯一的,也就是说相同的请求地址,一定只有一个@RequestMapping与之匹配 */
@Controller
//@RequestMapping("/requestMappingController")
public class RequestMappingController {
/**
* RequestMappingController类(控制器)加上@RequestMapping("/requestMappingController")
* 此时请求映射所映射的请求的请求路径为:/requestMappingController/testMethod
*/
@RequestMapping("/testMethod")
public String testRequestMapping(){
return "success";
}
}
3. @RequestMapping注解的value属性
<a th:href="@{/testValuePropertyForRequestMapping}">测试RequestMapping的value属性:"/testValuePropertyForRequestMapping"</a><br>
<a th:href="@{/testValue}">测试RequestMapping的value属性:"/testValue"</a><br>
/**
* @RequestMapping注解的value属性 通过客户端请求的请求地址来匹配请求映射
* 该请求映射能够匹配多个请求地址所对应的请求
*/
@RequestMapping(value = {"/testValuePropertyForRequestMapping", "/testValue"})
public String testValueProperty() {
return "success";
}
4. @RequestMapping注解的method属性
<a th:href="@{/testMethodForRequestMapping}">测试RequestMapping的method属性:GET</a><br>
<form th:action="@{/testMethodForRequestMapping}" method="post">
<input type="submit" value="测试RequestMapping的method属性:POST">
</form>
/**
* @RequestMapping注解的method属性
*/
@RequestMapping(value ="/testMethodForRequestMapping",method = {RequestMethod.GET})
public String testMethodProperty() {
return "success";
}
5. @RequestMapping注解的params属性
<!--请求参数可以加在()中-->
<a th:href="@{/testParamsPropertyForRequestMapping(loginname='admin',password='123')}">测试RequestMapping注解的params属性 -->/testParamsPropertyForRequestMapping</a>
/**
* @RequestMapping注解的params属性
*/
@RequestMapping(value = "/testParamsPropertyForRequestMapping", method = RequestMethod.GET,
params = {"loginname", "password!=123456"})
public String testParams() {
return "success";
}
6. @RequestMapping注解的headers属性
<a th:href="@{/testHeadersPropertyForRequestMapping}">测试RequestMapping注解的params属性 -->/testHeadersPropertyForRequestMapping</a><br>
@RequestMapping(value = "/testHeadersPropertyForRequestMapping", method = RequestMethod.GET,
headers = {"Host=localhost:8080"})
public String testHeaders() {
return "success";
}
7. SpringMVC支持ant风格的路径
<a th:href="@{/ant/testAnt}">测试@RequestMapping可以匹配ant风格的路径:使用?</a><br>
<a th:href="@{/antant/testAnt}">测试@RequestMapping可以匹配ant风格的路径:使用* </a><br>
<a th:href="@{/test/ant/testAnt}">测试@RequestMapping可以匹配ant风格的路径:使用** </a><br>
/**
* SpringMVC支持ant风格的路径
* ?,表示任意的单个字符; *,表示任意的0个或多个字符, **,表示任意的一层或多层目录
* @RequestMapping("/a?t/testAnt")
* @RequestMapping("/a*t/testAnt")
*/
@RequestMapping("/**/testAnt")
public String testAnt() {
return "success";
}
8. SpringMVC支持路径中的占位符(重点)
<a th:href="@{/testRestPath/1/xiaoming}">测试@RequestMapping支持路径中的占位符:使用/testRestPath </a>
/**
* SpringMVC支持路径中的占位符,如果用的是占位符,在发送请求的时候,请求地址必须要和@RequestMapping()中的value进行匹配
* 也就是说,@RequestMapping中有占位符,那么在请求地址中也必须有这一层
* "/"在路径里面表示路径的分隔符,路径里面也是用"/"表示一层一层的目录
*/
@RequestMapping("/testPath/{id}/{name}")
public String testPath(@PathVariable("id") Integer id, @PathVariable("name") String name)
{
System.out.println("id:" + id + "name:" + name);
return "success";
}
四、SpringMVC获取请求参数
1. 通过ServletAPI获取
<a th:href="@{/testServletAPI(username='zhangsan',password=123)}">测试使用ServletAPI获取请求参数</a><br>
/**
* 这里可以直接使用HttpServletRequest对象
* 控制器方法是DispatcherServlet底层中调用的,它可以根据当前控制器方法的形参,为它们注入不同的值
* 当前形参是HttpServletRequest对象,它就可以将当前DispatcherServlet中所获得的表示当前请求request的对象赋值给形参request
* 形参位置的request表示当前请求
*/
@RequestMapping("/testServletAPI")
public String testServletAPI(HttpServletRequest request){
String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.println("username:"+username+",password:"+password);
return "success";
}
2. 通过控制器方法的形参获取请求参数
<a th:href="@{/testParam(username='lisi',password=123456)}">测试使用控制器形参获取请求参数</a><br>
<form th:action="@{/testParam}" method="post">
用户名:<input type="text" name="username"><br>
密码: <input type="password" name="password"><br>
爱好: <input type="checkbox" name="hobby" value="sing">sing
<input type="checkbox" name="hobby" value="swim">swim
<input type="checkbox" name="hobby" value="basketball">basketball<br>
<input type="submit" value="测试使用控制器形参获取请求参数">
</form>
/**
*若请求参数中出现多个同名的请求参数(例如复选框勾选多个选项),在控制器方法相应的形参位置上声明字符串类型或字符串数组类型的参数来接收此请求参数
*若使用字符串数组类型的形参,此参数的数组中包含了每一个数据
*若使用字符串类型的形参,最终接收到的值为:同名的请求参数值之间使用逗号进行拼接,例如:"sing,swim,basketball"
*/
@RequestMapping("/testParam")
public String testParam(String username, String password,String hobby) {
System.out.println("username:" + username + ",password:" + password + ",hobby:" + hobby);
return "success";
}
3. @RequestParam
4. @RequestHeader
5. @CookieValue
<a th:href="@{/testCreateSession}">创建session</a><br>
<form th:action="@{/testRequestParam}" method="get">
用户名:<input type="text" name="username"><br>
密码: <input type="password" name="password"><br>
爱好: <input type="checkbox" name="hobby" value="sing">sing
<input type="checkbox" name="hobby" value="swim">swim
<input type="checkbox" name="hobby" value="basketball">basketball<br>
<input type="submit" value="测试使用控制器形参获取请求参数:@RequestParam">
</form>
/**
* HttpSession是一种保存少量信息至服务器端的一种技术,第一请求时,服务器会创建HttpSession,
* 我们可以在HttpSession对象中保存一些关于用户的状态信息,并将HttpSession的JSESSIONID以Cookie形式响应给浏览器,
* 第二次请求,浏览器会携带之前的JSESSIONID的Cookie,发送给服务器,服务器根据JSESSIONID获取对应的HttpSession对象.
*/
@RequestMapping("/testCreateSession")
public String getSession(HttpServletRequest request){
HttpSession session = request.getSession();
return "success";
}
/**
* 在此请求之前需要先通过上面请求映射为@RequestMapping("/testCreateSession")的控制器方法,创建HttpSession对象,
* 并将HttpSession的JSESSIONID以Cookie形式响应给浏览器,这里才能获取到JSESSIONID
*/
@RequestMapping("/testRequestParam")
public String testReqParam(@RequestParam(value = "username",required = false,defaultValue = "admin") String user_name,
String password, String hobby,
@RequestHeader("Host") String host,
@CookieValue("JSESSIONID") String JSESSIONID)
{
System.out.println("username:" + user_name + ",password:" + password + ",hobby:" + hobby);
System.out.println("host:" + host);
System.out.println("JSESSIONID:" + JSESSIONID);
return "success";
}
6. 通过POJO获取请求参数
<form th:action="@{/testBean}" 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" th:value="使用实体类接收请求参数">
</form>
/**
* 通过POJO获取请求参数
*/
@RequestMapping("/testBean")
public String testBean(User user) {
System.out.println(user);
return "success";
}
7. 解决获取请求参数的乱码问题
解决获取请求参数的乱码问题,可以使用SpringMVC提供的编码过滤器CharacterEncodingFilter,但是必须在web.xml中进行注册
<!--配置SpringMVC的编码过滤器-->
<filter>
<filter-name>CharacterEncodingFilter</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>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String encoding = this.getEncoding();
if (encoding != null) {
if (this.isForceRequestEncoding() || request.getCharacterEncoding() == null) {
request.setCharacterEncoding(encoding);
}
if (this.isForceResponseEncoding()) {
response.setCharacterEncoding(encoding);
}
}
filterChain.doFilter(request, response);
}
关于乱码问题
在设置请求对象的编码的时候有一个前提条件:如果在此之前,已经获取了某一个请求参数,那我们设置的编码是没有任何作用的。由于在DispatcherServlet中就会获取客户端的请求参数,所以,我们在控制器方法中设置编码是不起作用的,因此,要在DispatcherServlet获取请求参数之前来设置编码。那有什么组件比Servlet更早执行呢?答案是过滤器 。Web服务器中三大组件:监听器、过滤器、Servlet,加载时间最早的是监听器,其次是过滤器,最后才是Servlet。对于过滤器,我们设置了过滤路径,只要我们访问的请求地址满足过滤路径,都会被过滤器进行过滤。
五、域对象共享数据
1. 使用ServletAPI向request域对象共享数据
<a th:href="@{/testRequestScope}">使用ServletAPI向request域对象共享数据</a><br/>
@RequestMapping("/testRequestScope")
public String testRequestScopeByServletAPI(HttpServletRequest request){
request.setAttribute("testRequestScope","RequestScope");
//这里就属于请求转发,转发到success.html页面
return "success";
}
<!--在html页面中,只有"th:"所对应的属性中的内容才会被thymeleaf解析
不能 <p>${testRequestScope}</p>这样写,这样只会把${testRequestScope}当成<p></p>标签中的文本来解析
-->
<p th:text="${testRequestScope}"></p>
<p th:text="${session.testSessionScope}"></p>
<p th:text="${application.testApplicationScope}"></p>
2. 使用ModelAndView向request域对象共享数据
<a th:href="@{/testModuleAndView}">使用ModelAndView向request域对象共享数据</a><br/>
/**
* 实现页面跳转,应该是由控制器方法来设置视图名称,视图名称由视图解析器解析之后,找到最终的页面
* 所以,在控制器方法中,针对视图部分只需要设置一个视图名称即可
*/
@RequestMapping("/testModuleAndView")
public ModelAndView testModelAndView(){
//ModelAndView有Model和View的功能,Model主要用于向请求域共享数据,View主要用于设置视图,实现页面跳转
ModelAndView mav = new ModelAndView();
//处理模型数据,即向请求域request共享数据
mav.addObject("testRequestScope","ModelAndView");
//设置视图,实现页面跳转
mav.setViewName("success");
//将封装了模型和视图的ModelAndView对象作为方法的返回值,返回给DispatcherServlet,DispatcherServlet去解析
return mav;
}
3.使用Model向request域对象共享数据
<a th:href="@{/testModel}">使用Model向request域对象共享数据</a><br/>
@RequestMapping("/testModel")
public String testModel(Model model) {
model.addAttribute("testRequestScope", "Model");
//都要在控制器方法中创建相对应的形参,而形参具有向request域中共享数据的功能
System.out.println(model.getClass().getName());
return "success";
}
4. 使用map向request域对象共享数据
<a th:href="@{/testMap}">使用Map向request域对象共享数据</a><br/>
@RequestMapping("/testMap")
public String testMap(Map<String,Object> map){
map.put("testRequestSocpe","Map");
//都要在控制器方法中创建相对应的形参,而形参具有向request域中共享数据的功能
System.out.println(map.getClass().getName());
return "success";
}
5. 使用ModelMap向request域对象共享数据
<a th:href="@{/testModelMap}">使用ModelMap向request域对象共享数据</a><br/>
public String testModelMap(ModelMap modelMap){
modelMap.addAttribute("testRequestSocpe","ModelMap");
//都要在控制器方法中创建相对应的形参,而形参具有向request域中共享数据的功能
System.out.println(modelMap.getClass().getName());
return "success";
}
6. Model、ModelMap、Map的关系
public interface Model{}
public class ModelMap extends LinkedHashMap<String, Object> {}
public class ExtendedModelMap extends ModelMap implements Model {}
public class BindingAwareModelMap extends ExtendedModelMap {}
7. 向session域共享数据
<a th:href="@{/testSession}">使用ServletAPI向session域对象共享数据</a><br/>
@RequestMapping("/testSession")
public String testSession(HttpSession session){
session.setAttribute("testSessionScope","HttpSession");
return "success";
}
8. 向application域共享数据
<a th:href="@{/testApplication}">使用ServletAPI向applications域对象共享数据</a><br/>
/**
* 使用ServletAPI向application域共享数据
*/
@RequestMapping("/testApplication")
public String testApplication(HttpSession session){
ServletContext servletContext = session.getServletContext();
servletContext.setAttribute("testApplicationScope","ServletContext");
return "success";
}
六、SpringMVC的视图
1. ThymeleafView
@RequestMapping("/testThymeleafView")
public String testThymeleafView(){
return "success";
}
2. 转发视图
@RequestMapping("/testInternalResourceView")
public String testInternalResourceView(){
return "forward:/testThymeleafView";
}

@RequestMapping("/testInternalResourceView")
public String testInternalResourceView(){
return "forward:/WEB-INF/templates/success.html";
}
以上写法是错误的,我们在使用html页面时,一般会结合Thymeleaf处理后端发来的数据,因此,多数情况必不可少的会使用"th:"有关的标签。因为在html页面中,只有"th:"所对应的属性中的内容才会被Thymeleaf解析。当视图名称以"forward:"为前缀,生成的不是ThymeleafView视图,而是InternetResourceView视图。如果是直接转发到该页面,那就不能被配置的Thymeleaf视图解析器解析到,也不能生成ThymeleafView,而页面又使用了Thymeleaf,所以,"th:"所对应的属性中的内容不会被解析,也就不能访问到该页面了。
如果使用的是jsp页面,那么就不需要考虑有关Thymeleaf的视图解析器了,正常使用即可。
3. 重定向视图
@RequestMapping("/testRedirectView")
public String testRedirectView(){
return "redirect:/testThymeleafView";
}
说明:重定向视图在解析时,会先将redirect:前缀去掉,然后会判断剩余部分是否以/开头,若是则会自动拼接上下文路径
注意点与转发类似,但是重定向是两次请求,如果要重定向到WEB-INF目录下的页面,那就相当于从浏览器直接发送了一次访问WEB-INF目录的请求,而浏览器是无法直接访问WEB-INF目录下的页面的,所以只能通过重定向到其它方法,在由其它方法使用转发,经过ThymeleafView的视图解析器解析来到WEB-INF目录下。
4. 视图控制器view-controller
<!--视图控制器view-controller
当控制器方法中,仅仅用来实现页面跳转,并不需要处理模型数据时,可以将控制器方法使用view-controller标签进行表示
path:设置处理的请求地址,相当于@RequestMapping中的path
view-name:设置请求地址所对应的视图名称
例:以下标签用来代替TestController中的index()这个控制器方法
-->
<mvc:view-controller path="/" view-name="index"></mvc:view-controller>
@Controller
public class TestController {
@RequestMapping("/")
public String index() {
return "index";
}
}
说明:当SpringMVC中设置任何一个view-controller时,其它控制器中的请求映射将全部失效,此时需要在SpringMVC的核心配置文件中设置开启mvc注解驱动的标签:<mvc:annotation-driven />
七、RESTful
1. RESTful简介
2. RESTful的实现
当我们操作同一个资源,例如,操作的资源为用户信息,可以对其添加、删除、修改,无论是添加用户信息、删除用户信息还是修改用户信息,我们所操作的资源都是用户信息,用了RESTful后,既然操作的资源相同,那请求路径就应该相同。请求路径相同,怎么表示操作这些资源的方式3?具体说,就是 HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。 它们分别对应四种基本操作:GET 用来获取资源,POST 用来新建资源,PUT 用来更新资源,DELETE 用来删除资源。 REST 风格提倡 URL 地址使用统一的风格设计,从前到后各个单词使用斜杠分开,不使用问号键值对方式携带请求参数,而是将要发送给服务器的数据作为 URL 地址的一部分,以保证整体风格的一致性。
操作
|
传统方式
|
REST
风格
|
查询操作
|
getUserById?id=1
|
user/1-->get请求方式
|
保存操作
|
saveUser
|
user-->post请求方式
|
删除操作
|
deleteUser?id=1
|
user/1-->delete请求方式
|
更新操作
|
updateUser
|
user-->put请求方式
|
3. HiddenHttpMethodFilter
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
HttpServletRequest requestToUse = request;
if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
String paramValue = request.getParameter(this.methodParam);
if (StringUtils.hasLength(paramValue)) {
String method = paramValue.toUpperCase(Locale.ENGLISH);
if (ALLOWED_METHODS.contains(method)) {
requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
}
}
}
filterChain.doFilter((ServletRequest)requestToUse, response);
}
● HiddenHttpMethodFilter过滤器需要在web.xml中注册
实现步骤:
<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>
2. 编写控制器方法
/**
* 使用RESTful模拟操作用户资源(增删改查)
* /user GET 查询所有用户信息
* /user/1 GET 根据用户id查询用户信息
* /user POST 添加用户信息
* /user/1 DELETE 删除用户信息
* /user PUT 修改用户信息
*
* 说明:RESTful是一种风格,REST风格提倡 URL地址使用统一的风格设计
* 无论是增加、删除、修改、还是查询我们访问的都是用户资源,既然访问的资源相同,那么访问路径也应该相同
* 根据请求方式的不同来实现对用户的不同操作
*/
@Controller
public class UserController {
@RequestMapping(value = "/user",method = RequestMethod.GET)
public String getAllUsers(){
System.out.println("查询所有用户信息");
return "success";
}
@RequestMapping(value = "/user/{id}",method = RequestMethod.GET)
public String getUserById(@PathVariable("id") String id) {
System.out.println("根据"+ id + "查询用户信息");
return "success";
}
@RequestMapping(value = "/user",method = RequestMethod.POST)
public String addUser(String userName,String password){
System.out.println("添加用户信息为:"+userName+","+password);
return "success";
}
@RequestMapping(value = "/user",method = RequestMethod.PUT)
public String updateUser(String userName,String password){
System.out.println("修改用户信息为:"+userName+","+password);
return "success";
}
}
3. 配置页面请求
<a th:href="@{/user}">查询所有用户信息</a><br>
<a th:href="@{/user/1}">根据id查询用户信息</a><br>
<form th:action="@{/user}" method="post">
用户名:<input type="text" name="userName"><br>
密码: <input type="password" name="password"><br>
<input type="submit" value="添加">
</form>
<!--由于浏览器只支持发送get和post方式的请求,那么该如何发送put和delete请求呢?
SpringMVC提供了HiddenHttpMethodFilter帮助我们将POST请求转换为DELETE或PUT请求
要想发送put和delete请求,要满足两个条件:
1.请求方式为post 2.必须传输一个请求参数,参数名为_method,参数值是请求方式
-->
<form th:action="@{/user}" method="post">
<input type="hidden" name="_method" value="PUT">
用户名:<input type="text" name="userName"><br>
密码: <input type="password" name="password"><br>
<input type="submit" value="修改">
</form>
4. CharacterEncodingFilter和HiddenHttpMethodFilter的配置顺序
当有多个过滤器的时候,过滤器的执行顺序由 <filter-mapping>的顺序决定,先配置的先执行。目前为止,SpringMVC中提供了两个过滤器:CharacterEncodingFilter和HiddenHttpMethodFilter。在web.xml中注册时,必须先注册CharacterEncodingFilter,再注册HiddenHttpMethodFilter。原因:设置编码是有前提条件的,在CharacterEncodingFilter中是通过 request.setCharacterEncoding(encoding) 方法设置字符集的,而request.setCharacterEncoding(encoding) 方法要求前面不能有任何获取请求参数的操作,如果有获取请求参数的操作,编码设置就没有效果了,而HiddenHttpMethodFilter 恰恰有一个获取请求方式的操作:String paramValue = request.getParameter(this.methodParam);
<!--配置SpringMVC编码过滤器-->
<filter>
<filter-name>CharacterEncodingFilter</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>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--配置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>
八、RESTful案例
1. 功能清单
功能
|
URL
地址
|
请求方式
|
访问首页
|
/
|
GET
|
查询全部数据
|
/employee
|
GET
|
删除
|
/employee/2
|
DELETE
|
跳转到添加数据页面
|
/toAdd
|
GET
|
执行保存
|
/employee
|
POST
|
跳转到更新数据页面
|
/employee/2
|
GET
|
执行更新
√
|
/employee
|
PUT
|
2. 准备工作
package bean;
public class Employee {
private Integer id;
private String lastName;
private String email;
//1 male, 0 female
private Integer gender;
public Employee() {
}
public Employee(Integer id, String lastName, String email, Integer gender) {
super();
this.id = id;
this.lastName = lastName;
this.email = email;
this.gender = gender;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
}
package com.cgc.mvc.dao;
import bean.Employee;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@Repository
public class EmployeeDao {
private static Map<Integer, Employee> employees;
private static Integer initId = 107;
static{
employees = new HashMap<>();
employees.put(101,new Employee(101, "张先生", "zhang@163.com", 1));
employees.put(102,new Employee(102, "王女士", "wang@163.com", 0));
employees.put(103,new Employee(103, "李先生", "li@163.com", 1));
employees.put(104,new Employee(104, "赵先生", "zhao@163.com", 1));
employees.put(105,new Employee(105, "孙女士", "sun@163.com", 0));
employees.put(106,new Employee(106, "李女士", "li@163.com", 0));
}
public Collection<Employee> getAll(){
return employees.values();
}
public Employee get(Integer id){
return employees.get(id);
}
public void delete(Integer id){
employees.remove(id);
}
public void save(Employee employee){
if(employee.getId() == null){
employee.setId(initId++);
}
employees.put(employee.getId(),employee);
}
}
3. 功能实现
3.1. 访问首页
<!--配置视图控制器-->
<mvc:view-controller path="/" view-name="index"></mvc:view-controller>
<!--开启mvc注解驱动-->
<mvc:annotation-driven/>
● 创建页面,在templates下创建index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>首页</h1>
<a th:href="@{/employee}">查询员工信息</a>
</body>
</html>
3.2. 查询所有员工信息
● 控制器方法
/**
* 注解+扫描的方式,当前注解所标识的类才会被作为组件进行管理
*/
@Controller
public class EmployController {
@Autowired
private EmployeeDao employeeDao;
@RequestMapping(value = "/employee",method = RequestMethod.GET)
public String getAllEmployees(Model model){
Collection<Employee> employeeList = employeeDao.getAll();
model.addAttribute("employeeList",employeeList);
return "employees";
}
}
● 创建employees.html页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>员工信息</title>
<!--引入vue.js-->
<script type="text/javascript" th:src="@{/static/js/vue.js}"></script>
</head>
<body>
<table border="1" cellpadding="0" cellspacing="0" style="text-align:
center;" id="dataTable">
<tr>
<th colspan="5">Employee Info</th>
</tr>
<tr>
<th>id</th>
<th>name</th>
<th>email</th>
<th>gender</th>
<th>options(<a th:href="@{/toAdd}">add</a>)</th>
</tr>
<tr th:each="employee : ${employeeList}">
<td th:text="${employee.id}"></td>
<td th:text="${employee.name}"></td>
<td th:text="${employee.email}"></td>
<td th:text="${employee.gender}"></td>
<td>
<!--<a @click="deleteEmployee" th:href="@{/employee/}+${employee.id}">delete</a>-->
<!--两种方式都可以-->
<a @click="deleteEmployee" th:href="@{'/employee/'+${employee.id}}">delete</a>
<a th:href="@{'/employee/'+${employee.id}}">update</a>
</td>
</tr>
</table>
<!-- 创建处理delete请求方式的表单,作用:通过超链接控制表单的提交,将post请求转换为delete请求 -->
<form id="delete_form" method="post">
<!-- HiddenHttpMethodFilter要求:必须传输_method请求参数,并且值为最终的请求方式 -->
<input type="hidden" name="_method" value="delete"/>
</form>
<!--超链接控制表单提交,可以使用vue-->
<script type="text/javascript">
var vue = new Vue({
//挂载到需要操作的表上
el:"#dataTable",
methods:{
//event表示当前事件
deleteEmployee:function (event) {
//通过id获取表单标签
var delete_form = document.getElementById("delete_form");
//将触发点击事件的超链接的href属性为表单的action属性赋值
delete_form.action = event.target.href;
//提交表单
delete_form.submit();
//阻止超链接的默认跳转行为
event.preventDefault();
}
}
});
</script>
</body>
</html>
<!--
删除用的是超链接,但是超链接不能发送delete请求,发送delete请求需要满足的条件:
1.请求方式post 2.传输请求参数:_method
所以,通过vue,点击超链接时,去控制一个表单的提交,进而可以发送delete请求
-->
3.3. 删除功能
删除某员工信息,需要使用超链接时,可以使用vue来阻止超链接跳转并且控制表单的提交,从而实现删除功能。通过超链接的点击事件【vue】来提交表单【post+隐藏域put/delete】,然后请求被filter拦截,再被前端控制器解析,解析过后来到Controller进行匹配。
① 删除超链接,绑定点击事件
● 在webapp文件夹下创建static目录和js子目录,用于存放静态资源,放入vue.js文件,并在index.html中引入vue.js
<script type="text/javascript" th:src="@{/static/js/vue.js}"></script>
● 删除超链接
<a @click="deleteEmployee" th:href="@{'/employee/'+${employee.id}}">delete</a>
● 创建处理delete请求方式的表单
<!-- 创建处理delete请求方式的表单,作用:通过超链接控制表单的提交,将post请求转换为delete请求 -->
<form id="delete_form" method="post">
<!-- HiddenHttpMethodFilter要求:必须传输_method请求参数,并且值为最终的请求方式 -->
<input type="hidden" name="_method" value="delete"/>
</form>
● 通过vue处理点击事件
!--超链接控制表单提交,可以使用vue-->
<script type="text/javascript">
var vue = new Vue({
//挂载到需要操作的表上
el:"#dataTable",
methods:{
//event表示当前事件
deleteEmployee:function (event) {
//通过id获取表单标签
var delete_form = document.getElementById("delete_form");
//将触发点击事件的超链接的href属性为表单的action属性赋值
delete_form.action = event.target.href;
//提交表单
delete_form.submit();
//阻止超链接的默认跳转行为
event.preventDefault();
}
}
});
</script>
● 在SpringMVC.xml中,增加配置项:开放对静态资源的访问
<!--开放对静态资源的访问
工作流程:1.请求在被处理的过程中,请求会先被SpringMVC的前端控制器DispatcherServlet来处理
2.如果在控制器中找不到对应的请求映射,就会交给默认的Servlet来处理,如果默认的Servlet能找到相对应的资源,就访问资源
如果找不到相对应的资源就会404
-->
<mvc:default-servlet-handler/>
<!--开启mvc注解驱动-->
<mvc:annotation-driven/>
说明:加上<mvc:default-servlet-handler />表示如果当前浏览器发送的请求DispatcherServlet处理不了就会交给DefaultServlet来处理,同时需要配置 <mvc:annotation-driven/>,如果不配置,当前所有的请求都将被默认的DefaultServlet处理。
SpringMVC.xml中,如果没有<mvc:default-servlet-handler/>这个配置项,进行删除的时候会报405。
在Console(控制台)中,发现vue.js找不到
原因:通过请求路径 http://localhost:8080/SpringMVC/employee 查询所有员工信息,返回employees.html视图页面,由于在employees.html视图页面中引入了vue.js,浏览器会再次发送请求获取vue.js,而在SpringMVC工程中,由于配置了前端控制器DispatcherServlet,并且设置匹配的请求路径为"/",会拦截除JSP之外的所有请求,因此,访问静态资源vue.js,也会由SpringMVC的前端控制器DispatcherServlet来处理,而静态资源是不能交给SpringMVC的前端控制器DispatcherServlet来处理的(控制器中没有静态资源的请求映射),需要由默认的Servlet:DefaultServlet来处理静态资源。DispatcherServlet处理请求的方式是根据请求地址去控制器中找到相对应的请求映射,而控制器中是没有写访问静态资源的请求映射的,所以找不到。
关于在SpringMVC.xml中配置<mvc:default-servlet-handler />的一些说明
在SpringMVC.xml中配置<mvc:default-servlet-handler />后,会在SpringMVC上下文中定义一个org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler,它会像一个检查员,对进入DispatcherServlet的URL进行筛查,如果发现是静态资源的请求,就将该请求转由Web应用服务器默认的Servlet处理,如果不是静态资源的请求,才由DispatcherServlet继续处理。
一般Web应用服务器默认的Servlet名称是"default",因此DefaultServletHttpRequestHandler可以找到它。如果你所用的Web应用服务器的默认Servlet名称不是"default",则需要通过default-servlet-name属性显示指定:
<mvc:default-servlet-handler default-servlet-name="所使用的Web服务器默认使用的Servlet名称" />
② 控制器方法
@RequestMapping(value = "/employee/{id}",method = RequestMethod.DELETE)
public String deleteEmployee(@PathVariable("id") Integer id){
employeeDao.delete(id);
return "redirect:/employee";
}
3.4. 添加功能
① 跳转到添加数据页面
<mvc:view-controller path="/toAdd" view-name="employee_add"/>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>add employee</title>
</head>
<body>
<form th:action="@{/employee}" method="post">
lastName:<input type="text" name="lastName"><br>
email:<input type="text" name="email"><br>
gender:<input type="radio" name="gender" value="1">male
<input type="radio" name="gender" value="0">female<br>
<input type="submit" value="add"><br>
</form>
</body>
</html>
② 执行保存
@RequestMapping(value = "/employee",method = RequestMethod.POST)
public String addEmployee(Employee employee){
employeeDao.save(employee);
return "redirect:/employee";
}
3.5. 修改功能
① 跳转到更新数据页面
● 修改超链接
<a th:href="@{'/employee/'+${employee.id}}">update</a>
● 控制器方法
@RequestMapping(value = "/employee/{id}",method =RequestMethod.GET)
public String getEmployeeById(@PathVariable("id") Integer id,Model model){
Employee employee = employeeDao.get(id);
model.addAttribute("employee",employee);
return "employee_update";
}
● 在templates下创建employee_update.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>update Employee</title>
</head>
<body>
<form th:action="@{/employee}" method="post">
<input type="hidden" name="_method" value="put">
<input type="hidden" name="id" th:value="${employee.id}">
lastName:<input type="text" name="lastName" th:value="${employee.lastName}">
<br>
email:<input type="text" name="email" th:value="${employee.email}"><br>
<!--
th:field="${employee.gender}"可用于单选框或复选框的回显
若单选框的value和employee.gender的值一致,则添加checked="checked"属性
-->
gender:<input type="radio" name="gender" value="1"
th:field="${employee.gender}">male
<input type="radio" name="gender" value="0"
th:field="${employee.gender}">female<br>
<input type="submit" value="update"><br>
</form>
</body>
</html>
② 执行更新
@RequestMapping(value = "/employee",method = RequestMethod.PUT)
public String updateEmployee(Employee employee){
employeeDao.save(employee);
return "redirect:/employee";
}
九、HttpMessageConverter
1. @RequestBody
<form th:action="@{/testRequestBody}" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<input type="submit" value="测试@RequestMapping">
</form>
/**
* 使用@RequestBody注解实现将请求报文转化为java对象(这里转化为String类型对象)
*/
@RequestMapping(value = "/testRequestBody")
public String testRequestBody(@RequestBody String requestBody){ //请求体是字符串
System.out.println("requestBody:"+requestBody);
return "success";
}
2. RequestEntity
<form th:action="@{/testRequestEntity}" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<input type="submit" value="测试testRequestEntity">
</form>
@RequestMapping(value = "/testRequestEntity")
public String testRequestEnty(RequestEntity<String> requestEntity){
//当前requestEntity表示整个请求报文的信息,泛型为String,表示以字符串的方式获取请求报文
System.out.println("请求头:"+requestEntity.getHeaders());
System.out.println("请求体:"+requestEntity.getBody());
return "success";
}
注:RequestEntity封装的是整个请求报文。
输出结果:
请求头:[host:"localhost:8080", connection:"keep-alive", content-length:"27", cache-control:"max-age=0", sec-ch-ua:""Google Chrome";v="113", "Chromium";v="113", "Not-A.Brand";v="24"", sec-ch-ua-mobile:"?0", sec-ch-ua-platform:""Windows"", upgrade-insecure-requests:"1", origin:"http://localhost:8080", user-agent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36", accept:"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", sec-fetch-site:"same-origin", sec-fetch-mode:"navigate", sec-fetch-user:"?1", sec-fetch-dest:"document", referer:"http://localhost:8080/SpringMVC/", accept-encoding:"gzip, deflate, br", accept-language:"zh-CN,zh;q=0.9", Content-Type:"application/x-www-form-urlencoded;charset=UTF-8"]
请求体:username=admin&password=123
概要说明:下面有关response,我们在学习完JavaWeb之后,我们可以清楚的感受到request的主要作用其实就是获取客户端请求的信息,有些信息是程序员需要看的,但是有的信息是计算机需要看的。程序员需要处理的就是类似于登录案例的username和password请求信息,而像浏览器版本信息之类的请求信息以及被高度封装的计算机处理,所以我们要处理的request信息并不多。但是,我们响应给浏览器的方式有很多,内容也十分复杂。比如,我们可以通过response的getwriter方法获取字符输出流将信息反馈输出到前端页面,这是一种响应,这种方法我们在学习Javaweb阶段一般不是响应页面显示时使用的,而是和异步请求AJAX(以及json)联合使用。另外一种响应就是response的重定向setRedirect以及request的转发。所以终点在于我们根据前台信息的逻辑判断反馈给浏览器的各种服务,response这块也就自然的变成了这部分学习的重点。
3. @ResponseBody
<a th:href="@{/testResponse}">通过ServletAPI的Response对象响应浏览器数据</a><br/>
<a th:href="@{/testResponseBody}">通过@ResponseBody响应浏览器数据</a><br/>
/**
* 在通过控制器处理请求时,要想对浏览器进行响应,有几种方式:
* 1.通过实现页面跳转(转发、重定向),响应给浏览器一个完整的页面
* 2.通过response.getWriter()获取一个PrintWriter对象,再通过其中的write()或print()响应浏览器
*/
@RequestMapping("/testResponse")
public void testResponseBody(HttpServletResponse response) throws IOException {
//操作响应体, 将print()中的内容:"测试Response",直接作为响应报文的响应体响应到浏览器
response.getWriter().print("测试Response");
}
/**
* 也就是说加了@ResponseBody注解,当前方法的返回值就是要响应到浏览器的数据
* 不加@ResponseBody, return "success", sucesss会作为视图名称,被视图解析器解析
* 加了@ResponseBody, return "success", sucesss表示的不再是视图名称,而是当前响应的响应体
*/
@RequestMapping("/testResponseBody")
@ResponseBody
public String testResponseBody(){
return "success";
}
4. SpringMVC处理json
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.1</version>
</dependency>
2)在SpringMVC的核心配置文件中开启mvc的注解驱动,此时在HandlerAdaptor中会自动装配一个消息转换器:MappingJackson2HttpMessageConverter,可以将响应到浏览器的Java对象转换为Json格式的字符串
<mvc:annotation-driven />
3)在处理器方法上使用@ResponseBody注解进行标识
4)将Java对象直接作为控制器方法的返回值返回,就会自动转换为Json格式的字符串
/**
* 服务器是不能直接响应浏览器对象的,因为浏览器并不能识别我们响应的java对象,只能接收文本(浏览器不知道服务器用的是什么语言)
* 而http协议就是将浏览器端和服务器端做一个统一的规范,和浏览器的种类、编程语言的种类无关,所以,要按照请求报文和响应报文的格式发送请求和响应才行
*
* 当前既要保留Student对象中的各个数据,还要让它能够正常的响应到浏览器,怎么做?转换为json
* 这里要将控制器方法的返回值转换为json格式的,json是一种数据交互格式,区分json对象和数组:最外层是{}是json对象,最外层是[]是json数组
*/
@RequestMapping("/testResponseStudent")
@ResponseBody
public Student testResponseStudent(){
return new Student(1,"admin",20,"1001");
}
注:json是一种数据交互格式,xml也是一种数据交互格式。数据交互格式现在用的最多的是json,xml用的较多的是作为配置文件。
- JSON 指的是 JavaScript 对象表示法(JavaScript Object Notation)
- JSON 是轻量级的文本数据交换格式
- JSON 独立于语言:JSON 使用 Javascript语法来描述数据对象,但是 JSON 仍然独立于语言和平台。JSON 解析器和 JSON 库支持许多不同的编程语言。C、Python、C++、Java、PHP、Go等编程语言都支持 JSON。
- JSON 具有自我描述性,更易理解
5. SpringMVC处理ajax
1)请求超链接
<!--ajax在页面不发生跳转的情况下(不刷新)与服务器进行交互-->
<div id="app">
<a th:href="@{/testAjax}" @click="testAjax">SpringMVC处理Ajax</a><br>
</div>
2)通过vue和axios处理点击事件
<script type="text/javascript" th:src="@{/static/js/vue.js}"></script>
<script type="text/javascript" th:src="@{/static/js/axios.min.js}"></script>
<script type="text/javascript">
var vue = new Vue({
//与DOM的id进行绑定,相互关联
el:"#app",
methods:{
testAjax:function (event) {
axios({
method:"post",
url:event.target.href,
params:{
username:"admin",
password:"123456"
}
}).then(function (response) {
alert(response.data);
});
event.preventDefault();
}
}
});
</script>
/**
* ajax:页面不刷新与服务器进行交互,所以在服务器中不能用转发和重定向
* 只能使用响应浏览器数据
*/
@RequestMapping("/testAjax")
@ResponseBody
public String testAjax(String username, String password){
System.out.println("username:"+username+",password:"+password);
return "hello,ajax";
}
补充:
● AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。
● AJAX 不是新的编程语言,而是一种使用现有标准的新方法。
● AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容。
● AJAX 不需要任何浏览器插件,但需要用户允许 JavaScript 在浏览器上执行。
● XMLHttpRequest 只是实现 Ajax 的一种方式
6. @RestController注解
7. ResponseEntity
十、文件上传和下载
1. 文件下载
/**
* 文件上传和下载本质上是一个文件复制的过程
*/
@Controller
public class FileUpLoadAndDownLoadController {
@RequestMapping("/testDownload")
public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws
IOException {
//获取ServletContext对象
ServletContext servletContext = session.getServletContext();
//获取服务器中文件的真实路径
String realPath = servletContext.getRealPath("/static/img/1.jpg");
System.out.println(realPath);
//创建输入流
InputStream is = new FileInputStream(realPath);
//创建字节数组, is.available():获取输入流所对应的文件所有的字节
byte[] bytes = new byte[is.available()];
//将输入流所对应的文件中的所有字节读到字节数组中
is.read(bytes);
//创建HttpHeaders对象设置响应头信息
MultiValueMap<String, String> headers = new HttpHeaders();
//设置要下载方式以及下载文件的名字, attachment:以附件的方式来下载文件 filename:为下载的文件设置的默认的名字
//只有filename可以改,其它都是固定的
headers.add("Content-Disposition", "attachment;filename=1.jpg");
//设置响应状态码
HttpStatus statusCode = HttpStatus.OK;
//创建ResponseEntity对象, ResponseEntity可以自定义一个响应报文响应浏览器
//bytes存放了当前要下载的文件中所有的字节,也就是响应体
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, headers, statusCode);
//关闭输入流
is.close();
return responseEntity;
}
}
2. 文件上传
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<!--文件上传解析器,必须通过文件解析器的解析才能将文件转换为MultipartFile对象-->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
</bean>
/**
* SpringMVC把当前上传的文件封装成了MultipartFile对象
*/
@RequestMapping("/testUp")
public String testUp(MultipartFile photo, HttpSession session) throws
IOException {
//获取上传的文件的文件名
String fileName = photo.getOriginalFilename();
//处理上传相同文件重名问题
String hzName = fileName.substring(fileName.lastIndexOf("."));
fileName = UUID.randomUUID().toString() + hzName;
//通过ServletContext获取服务器中photo目录的路径
ServletContext servletContext = session.getServletContext();
String photoPath = servletContext.getRealPath("photo");
File file = new File(photoPath);
if (!file.exists()) {
file.mkdir();
}
//文件最终上传的路径,上传也是文件复制,先读再写,写的时候需要知道上传到哪个目录,以及这个文件叫什么
String finalPath = photoPath + File.separator + fileName;
//实现上传功能
photo.transferTo(new File(finalPath));
return "success";
}
十一、拦截器
1. 拦截器的配置
只是实现HandlerInterceptor接口,SpringMVC并不会将它识别为一个拦截器,SpringMVC的拦截器必须在SpringMVC的配置文件中进行配置
public class MyFirstInterceptor implements HandlerInterceptor {
/**
* 控制器方法执行之前执行, 对控制器方法进行拦截。返回值:false表示拦截,true表示放行
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor的preHandle方法已执行");
return true;
}
/**
* 控制器方法执行之后执行
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor的postHandle方法已执行");
}
/**
* 处理完视图和模型数据,渲染视图完毕之后执行
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyInterceptor的afterCompletion方法已执行");
}
}
<!--配置拦截器-->
<mvc:interceptors>
<!--通过<bean>标签声明,表示某一个类型的对象就是一个拦截器-->
<bean class="com.cgc.mvc.interceptor.MyFirstInterceptor "/>
</mvc:interceptors>
方式2:
@Component
public class MyFirstInterceptor implements HandlerInterceptor {
/**
* 控制器方法执行之前执行, 对控制器方法进行拦截。返回值:false表示拦截,true表示放行
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor的preHandle方法已执行");
return true;
}
/**
* 控制器方法执行之后执行
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor的postHandle方法已执行");
}
/**
* 处理完视图和模型数据,渲染视图完毕之后执行
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyInterceptor的afterCompletion方法已执行");
}
}
<!--配置拦截器-->
<mvc:interceptors>-->
<!-- 通过ref标签, 需要在xml中通过<bean>标签声明MyInterceptor的bean,或者在MyInterceptor类上加注解-->
<ref bean="myFirstInterceptor "></ref>-->
</mvc:interceptors>
以上两种配置方式都是对DispatcherServlet所处理的所有的请求进行拦截,无法设置拦截规则
方式3:
@Component
public class MyFirstInterceptor implements HandlerInterceptor {
/**
* 控制器方法执行之前执行, 对控制器方法进行拦截。返回值:false表示拦截,true表示放行
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor的preHandle方法已执行");
return true;
}
/**
* 控制器方法执行之后执行
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor的postHandle方法已执行");
}
/**
* 处理完视图和模型数据,渲染视图完毕之后执行
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyInterceptor的afterCompletion方法已执行");
}
}
<!--配置拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<!--设置拦截的路径:"/*"只表示访问上下文路径下面的一层目录 "/**"拦截所有 -->
<mvc:mapping path="/**"/>
<!--要排除的请求映射-->
<mvc:exclude-mapping path="/"/>
<!--指定拦截器-->
<ref bean="myFirstInterceptor"></ref>
</mvc:interceptor>
</mvc:interceptors>
<!--以上配置方式可以通过ref或bean标签设置拦截器,通过mvc:mapping设置需要拦截的请求,通过
mvc:exclude-mapping设置需要排除的请求,即不需要拦截的请求-->
2. 拦截器的三个抽象方法
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 = 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.
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);
}
}
}
}
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) {
//执行拦截器的afterComplation()
// Exception (if any) is already handled..
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
3. 多个拦截器的执行顺序
<!--配置拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<!--要排除的请求映射-->
<mvc:exclude-mapping path="/"/>
<!--指定拦截器-->
<ref bean="myFirstInterceptor"></ref>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/"/>
<ref bean="mySecondInterceptor"></ref>
</mvc:interceptor>
</mvc:interceptors>

3.2. 若某个拦截器的preHandle()返回了false,那这个拦截器的preHandle()和它之前的拦截器的preHandle()都会执行,postHandle()都不执行,preHandle()返回false的拦截器之前的拦截器的afterComplation()会执行

十二、异常处理器
1. 基于配置的异常处理
SpringMVC提供了一个处理控制器方法执行过程中出现异常的接口:HandlerExceptionResolver
SpringMVC中的异常处理器就是在控制器方法执行的过程中,如果出现了异常,就为它返回一个新的ModelAndView,跳转到指定页面。
HandlerExceptionResolver接口的实现类有:DefaultHandlerExceptionResolver和SimpleMappingExceptionResolver
DefaultHandlerExceptionResolver是SpringMVC默认使用的异常处理器,也就是说,在使用过程中出现的一些异常,SpringMVC都已经帮我们处理过了,而DefaultHandlerExceptionResolver中有一个doResolveException()方法。doResolveException()的作用就是:如果在控制器方法执行过程中出现了指定的异常,它就可以代替原来方法要返回的ModelAndView,返回一个新的ModelAndView,跳转到指定的页面,为我们显示异常信息。而ModelAndView就是用来处理模型数据和渲染视图的,所以有了ModelAndView就可以跳转到指定的页面了。
protected ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
try {
if (ex instanceof HttpRequestMethodNotSupportedException) {
return handleHttpRequestMethodNotSupported(
(HttpRequestMethodNotSupportedException) ex, request, response, handler);
}
else if (ex instanceof HttpMediaTypeNotSupportedException) {
return handleHttpMediaTypeNotSupported(
(HttpMediaTypeNotSupportedException) ex, request, response, handler);
}
else if (ex instanceof HttpMediaTypeNotAcceptableException) {
return handleHttpMediaTypeNotAcceptable(
(HttpMediaTypeNotAcceptableException) ex, request, response, handler);
}
else if (ex instanceof MissingPathVariableException) {
return handleMissingPathVariable(
(MissingPathVariableException) ex, request, response, handler);
}
else if (ex instanceof MissingServletRequestParameterException) {
return handleMissingServletRequestParameter(
(MissingServletRequestParameterException) ex, request, response, handler);
}
else if (ex instanceof ServletRequestBindingException) {
return handleServletRequestBindingException(
(ServletRequestBindingException) ex, request, response, handler);
}
else if (ex instanceof ConversionNotSupportedException) {
return handleConversionNotSupported(
(ConversionNotSupportedException) ex, request, response, handler);
}
else if (ex instanceof TypeMismatchException) {
return handleTypeMismatch(
(TypeMismatchException) ex, request, response, handler);
}
else if (ex instanceof HttpMessageNotReadableException) {
return handleHttpMessageNotReadable(
(HttpMessageNotReadableException) ex, request, response, handler);
}
else if (ex instanceof HttpMessageNotWritableException) {
return handleHttpMessageNotWritable(
(HttpMessageNotWritableException) ex, request, response, handler);
}
else if (ex instanceof MethodArgumentNotValidException) {
return handleMethodArgumentNotValidException(
(MethodArgumentNotValidException) ex, request, response, handler);
}
else if (ex instanceof MissingServletRequestPartException) {
return handleMissingServletRequestPartException(
(MissingServletRequestPartException) ex, request, response, handler);
}
else if (ex instanceof BindException) {
return handleBindException((BindException) ex, request, response, handler);
}
else if (ex instanceof NoHandlerFoundException) {
return handleNoHandlerFoundException(
(NoHandlerFoundException) ex, request, response, handler);
}
else if (ex instanceof AsyncRequestTimeoutException) {
return handleAsyncRequestTimeoutException(
(AsyncRequestTimeoutException) ex, request, response, handler);
}
}
catch (Exception handlerEx) {
if (logger.isWarnEnabled()) {
logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", handlerEx);
}
}
return null;
}
SpringMVC提供了自定义的异常处理器SimpleMappingExceptionResolver,让我们自定义异常处理。例如,如果当前控制器方法在执行的过程中出现了某些异常,我们就可以给它指定一个视图进行跳转。使用方式:
<!--配置异常处理-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<!--exceptionMappings是Properties类型的-->
<property name="exceptionMappings">
<!--赋值-->
<props>
<!--Properties的键表示控制器方法执行过程中出现的异常
Properties的值表示若出现指定异常时,设置一个新的视图名称,跳转到指定页面
-->
<prop key="java.lang.ArithmeticException">error</prop>
</props>
</property>
<!--exceptionAttribute属性,用于设置将出现的异常信息在请求域中进行共享的键,
异常信息默认是存储到当前的请求域中的,value就是存储到请求域中的异常信息的键
-->
<property name="exceptionAttribute" value="ex"/>
</bean>
2. 基于注解的异常处理
/**
* @ControllerAdvice注解,是用@Component来进行标识,所以@ControllerAdvice注解是@Component的一个扩展注解
* 所以具有将类标识为组件的功能
*
* @ControllerAdvice将当前类标识为异常处理的组件
*/
@ControllerAdvice
public class ExcepHandler {
/**
* @ExceptionHandler注解中可以添加参数,参数是某个异常类的class,代表这个方法专门处理该类异常,
* 如下此时注解的参数是ArithmeticException.class,NullPointerException.class,表示只有方法抛出ArithmeticException或者NullPointerException时,才会调用该方法
* 这个时候就会将@ExceptionHandler注解所标识的方法作为当前新的控制器方法来执行
*/
@ExceptionHandler(value = {ArithmeticException.class, NullPointerException.class})
public String testException(Exception ex, Model model) {
model.addAttribute("ex", ex);
return "error";
}
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
出现异常
<p th:text="${ex}"></p>
</body>
</html>
十三、注解配置SpringMVC
1. 创建初始化类,代替web.xml
/**
* web工程的初始化类,用来代替web.xml
*/
public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* 指定Spring的配置类
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[0];
}
/**
* 指定SpringMVC的配置类
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebConfig.class};
}
/**
* 指定DispatcherServlet的映射规则,即url-pattern
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
/**
* 添加过滤器
*/
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
characterEncodingFilter.setForceResponseEncoding(true);
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
return new Filter[]{characterEncodingFilter, hiddenHttpMethodFilter};
}
}
2. 创建SpringConfig配置类,代替spring的配置文件
/**
*
* SSM整合之后,Spring的配置信息写在此类中
*/
@Configuration
public class SpringConfig {
}
3. 创建WebConfig配置类,代替SpringMVC的配置文件
/**
* 可以在一个类上通过注解@Configuration将它标识为配置类,这个时候它就可以代替Spring的配置文件
* 代替SpringMVC的配置文件,之前在SpringMVC的配置文件中配置的内容有:
* 1.扫描组件 2.视图解析器 3.view-controller 4.default-servlet-handler
* 5.mvc注解驱动 6.文件上传解析器 7.异常处理 8.拦截器
*
* @Configuration将当前类标识为一个配置类
* @ComponentScan扫描组件
* @EnableWebMvc mvc注解驱动
*/
@Configuration
@ComponentScan("com.cgc.mvc")
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
/**
*使用默认的Servlet处理静态资源
*/
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
/**
* 配置拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
MyInterceptor myInterceptor = new MyInterceptor();
registry.addInterceptor(myInterceptor).addPathPatterns("/**");
}
/**
* 配置视图控制 view-controller
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
}
/**
* 配置文件上传解析器
*/
@Bean
public MultipartResolver multipartResolver() {
CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
return commonsMultipartResolver;
}
/**
* 配置异常处理器
*/
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
SimpleMappingExceptionResolver exceptionResolver = new
SimpleMappingExceptionResolver();
Properties prop = new Properties();
prop.setProperty("java.lang.ArithmeticException", "error");
//设置异常映射
exceptionResolver.setExceptionMappings(prop);
//设置共享异常信息的键
exceptionResolver.setExceptionAttribute("ex");
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;
}
/**
* 生成模板引擎并为模板引擎注入模板解析器
* templateEngine()方法是由Spring调用的,Spring在调用的时候会在IOC容器中找ITemplateResolver类型的bean,来为方法的参数赋值,相当于属性注入
* 所以,当前方法所使用的参数,必须符合自动装配的规则,也就是说,方法所能够使用的参数必须是当前SpringIOC容器中所拥有的bean,并且IOC容器中的bean能为此参数赋值
*/
@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;
}
}
4. 测试功能
@Controller
public class TestController {
@RequestMapping("/")
public String toIndex() {
return "index";
}
}
十四、SpringMVC执行流程
1. SpringMVC常用组件
2. DispatcherServlet初始化过程

● 初始化WebApplicationContext
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("Initializing Servlet '" + getServletName() + "'");
}
long startTime = System.currentTimeMillis();
try {
//对SpringMVC的IOC容器进行初始化
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
if (logger.isDebugEnabled()) {
String value = this.enableLoggingRequestDetails ?
"shown which may lead to unsafe logging of potentially sensitive data" :
"masked to prevent unsafe logging of potentially sensitive data";
logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
"': request parameters and headers will be " + value);
}
if (logger.isInfoEnabled()) {
logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
}
}
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
// 创建WebApplicationContext
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
synchronized (this.onRefreshMonitor) {
// 刷新WebApplicationContext
onRefresh(wac);
}
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
//将IOC容器在应用域共享
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
Class<?> contextClass = getContextClass();
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
//
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
//设置父容器
wac.setParent(parent);
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
configureAndRefreshWebApplicationContext(wac);
return wac;
}
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
//作用:请求处理完成后,返回的视图名称,要被视图解析器解析,找到相对应的视图渲染
initViewResolvers(context);
initFlashMapManager(context);
}
3. DispatcherServlet调用组件处理请求
● processRequest()
FrameworkServlet重写HttpServlet中的service()和doXxx(),这些方法中调用了processRequest(request, response)
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
processRequest(request, response);
}
else {
super.service(request, response);
}
}
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
initContextHolders(request, localeContext, requestAttributes);
try {
// 执行服务,处理请求和响应,doService()是一个抽象方法,在DispatcherServlet中进行了重写
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
logRequest(request);
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// Make framework objects available to handlers and view objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
RequestPath requestPath = null;
if (this.parseRequestPath && !ServletRequestPathUtils.hasParsedRequestPath(request)) {
requestPath = ServletRequestPathUtils.parseAndCache(request);
}
try {
// 处理请求和响应
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
if (requestPath != null) {
ServletRequestPathUtils.clearParsedRequestPath(request);
}
}
}
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);
}
}
}
}
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);
}
}
4. SpringMVC的执行流程
16:27:00.926 [http-nio-8080-exec-9] DEBUG org.springframework.web.servlet.DispatcherServlet - GET "/SpringMVC/demo", parameters={}
16:27:00.926 [http-nio-8080-exec-9] WARN org.springframework.web.servlet.PageNotFound - No mapping for GET /SpringMVC/demo
16:27:00.927 [http-nio-8080-exec-9] DEBUG org.springframework.web.servlet.DispatcherServlet - Completed 404 NOT_FOUND
客户端:
如果有配置,则访问目标资源(一般为静态资源,如:JS,CSS,HTML),找不到客户端也会展示404错误
控制台:
16:32:31.104 [http-nio-8080-exec-4] DEBUG org.springframework.web.servlet.DispatcherServlet - GET "/SpringMVC/demo", parameters={}
16:32:31.112 [http-nio-8080-exec-4] DEBUG org.springframework.web.servlet.handler.SimpleUrlHandlerMapping - Mapped to org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler@1e297064
16:32:31.119 [http-nio-8080-exec-4] DEBUG org.springframework.web.servlet.DispatcherServlet - Completed 404 NOT_FOUND
客戶端:
● 存在,则执行下面的流程