文章目录
RESTful
关于资源的概念
资源
资源是一种看待服务器的方式,即,将服务器看作是由很多离散的资源组成。每个资源是服务器上一个可命名的抽象概念。因为资源是一个抽象的概念,所以它不仅仅能代表服务器文件系统中的一个文件、数据库中的一张表等等具体的东西,可以将资源设计的要多抽象有多抽象,只要想象力允许而且客户端应用开发者能够理解。与面向对象设计类似,资源是以名词为核心来组织的,首先关注的是名词。一个资源可以由一个或多个URI来标识。URI既是资源的名称,也是资源在Web上的地址。对某个资源感兴趣的客户端应用,可以通过资源的URI与其进行交互。
URL和URI的区别和联系
什么是URI
URI就类似一个独一无二的身份标识。准确的说是某个网络资源的特有标识(用来区别于其他资源的独一无二的标识)
有这样一个需求:
要求找到一本书(书就是资源),这本书在A省份/B市/C区/D街道/xx栋/392-1住户/1号房间/名字叫做《xxx》 (这里就是模拟我们输入网址进行HTTP请求)
- 那么此时的 《xxx》 这本书 对于 A省份/B市/C区/D街道/xx栋/392-1住户/1号房间/ 来说就是URI
- 此时的D街道/xx栋/392-1住户/1号房间/名字叫做《xxx》这本书 对于 A省份/B市/C区 来说就是URI
- 可以看出URI是不固定的,是相对来说的,具体是什么就看你的参照角度是什么。(不同请求参照角度不一样,所以他们的返回uri有差异)
- 由此总结:URI是一个标识,用来区别于其他资源的标识。
什么是URL
那么再来说一说什么是URL。URL就是每次我们输入网址访问某个网站时,浏览器上输入的那一行内容。比如:http://baidu.com
这是一个URL
- 类比上面的例子,我们的A省份/B市/C区/D街道/xx栋/392-1住户/1号房间/名字叫做《xxx》就是一个URL,类似一个绝对地址,根据这个地址肯定能找到这本书
- 但是只根据URI,我们是找不到这本书的,必须有前提条件
- 我们的URL包含了协议,ip地址,端口号,和资源的在服务器下的相对路径,类似绝对路径,也就肯定能找到这个数据
URI和URL的关系
URI是URL的父级,URL是URI的子级。
明明是URL包含了URI为啥URI反而是父级
- 请注意,我这里用的是级别来描述,而不是包含。
- 我没有说URL是URI的一部分,而是说是他的子级。
- 想要理解这个概念,最好的说明就是Java的继承关系。URL继承了URI。
- 因为URL继承了所有URI的内容,所以它比URI更加详细,但是URI是它的父级。
有什么作用
URL的作用
- URL一般是一个完整的链接,我们可以直接通过这个链接(url)访问到一个网站或者对应的资源,或者把这个url复制到浏览器访问网站。
URI的作用
- URI并不是一个直接访问的链接,而是相对地址(当然如果相对于浏览器那么uri等同于url了)。这种概念更多的是用于编程中,因为我们没必要每次编程都用绝对url来获取一些页面,这样还需要进行分割“http://xx/xxx”前面那一串,所以编程的时候直接request.getRequestURI就行了,当然如果是重定向的话,就用URL。
资源的表述
资源的表述是一段对于资源在某个特定时刻的状态的描述。可以在客户端-服务器端之间转移(交换)。资源的表述可以有多种格式,例如HTML/XML/JSON/纯文本/图片/视频/音频等等。资源的表述格式可以通过协商机制来确定。请求-响应方向的表述通常使用不同的格式
RESTful简介
- REST:Representational State Transfer,表现层资源状态转移。。
RESTFUL是一种网络应用程序的设计风格和开发方式
,基于HTTP,可以使用 XML 格式定义或 JSON 格式定义。最常用的数据格式是JSON。由于JSON能直接被JavaScript读取,所以,使用JSON格式的REST风格的API具有简单、易读、易用的特点。
REST 是面向资源的,每个资源都有一个唯一的资源定位符(URI)。每个URI代表一种资源(resource),所以URI中不能有动词,只能有名词,而且所用的名词往往与数据库的表名对应。一般来说,数据库中的表都是同种记录的"集合"(collection),所以URI中的名词也应该使用复数。
RESTful的实现
- 具体说,就是 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请求方式 |
没有RESTful之前的用法
http://127.0.0.1/user/query/1 GET 根据用户id查询用户数据
http://127.0.0.1/user/save POST 新增用户
http://127.0.0.1/user/update POST 修改用户信息
http://127.0.0.1/user/delete/1 GET/POST 删除用户信息
RESTful用法:
http://127.0.0.1/user/1 GET 根据用户id查询用户数据
http://127.0.0.1/user POST 新增用户
http://127.0.0.1/user PUT 修改用户信息
http://127.0.0.1/user/1 DELETE 删除用户信息
之前的操作有什么问题呢?你每次请求的接口或者地址,都在做描述,例如查询的时候用了query,新增的时候用了save,其实完全没有这个必要,我使用了get请求,就是查询.使用post请求,就是新增的请求,我的意图很明显,完全没有必要做描述,这就是为什么有了restful.
HiddenHttpMethodFilter
由于浏览器只支持发送get和post方式的请求,那么该如何发送put和delete请求呢?
SpringMVC 提供了 HiddenHttpMethodFilter 帮助我们将 POST 请求转换为 DELETE 或 PUT 请求HiddenHttpMethodFilter 处理put和delete请求的条件:
- 当前请求的请求方式必须为post
- 当前请求必须传输请求参数_method
满足以上条件,HiddenHttpMethodFilter 过滤器就会将当前请求的请求方式转换为请求参数
- _method的值,因此请求参数_method的值才是最终的请求方式
- 在web.xml中注册HiddenHttpMethodFilter
目前为止,SpringMVC中提供了两个过滤器:CharacterEncodingFilter和HiddenHttpMethodFilter
在web.xml中注册时,必须先注册CharacterEncodingFilter,再注册HiddenHttpMethodFilter
原因:
在 CharacterEncodingFilter 中通过 request.setCharacterEncoding(encoding) 方法设置字符集的 request.setCharacterEncoding(encoding) 方法要求前面不能有任何获取请求参数的操作而 HiddenHttpMethodFilter 恰恰有一个获取请求方式的操作String paramValue = request.getParameter(this.methodParam);
实例
查询操作
@Controller
public class TestRestController {
@GetMapping("/user")
public String getAllUser(){
System.out.println("查询所有的用户信息-->/user-->get");
return "success";
}
}
//http://localhost:8080/springmvc/user 在浏览器直接输入,是get方法
//输出结果
查询所有的用户信息-->/user-->get
@Controller
public class TestRestController {
@GetMapping("user/{id}")
public String getUserById(@PathVariable("id") Integer id){
System.out.println("根据id查询用户信息-->/user/"+id+"-->get");
return "success";
}
}
//http://localhost:8080/springmvc/user/1 在浏览器直接输入,是get方法
//输出结果
根据id查询用户信息-->/user/1-->get
保存操作
<form th:action="@{/user}" th:method="post">
<input type="submit" value="测试Restful">
</form>
<!--利用表单发送post请求-->
@Controller
public class TestRestController {
@PostMapping("/user")
public String insertUser(){
System.out.println("添加用户信息-->/user-->post");
return "success";
}
}
//输出结果
添加用户信息-->/user-->post
更新操作
<!--利用表单和HiddenHttpMethodFilter来将post转换为别的请求-->
<form th:action="@{/user}" th:method="post">
<input type="hidden" name="_method" value="put">
<input type="submit" value="测试Restful的put">
</form>
@Controller
public class TestRestController {
@PutMapping("/user")
public String updateUser(){
System.out.println("修改用户信息-->/user-->put");
return "success";
}
}
//输出结果
修改用户信息-->/user-->put
删除操作
<form th:action="@{/user/5}" th:method="post">
<input type="hidden" name="_method" value="delete">
<input type="submit" value="测试Restful的delete">
</form>
@Controller
public class TestRestController {
@DeleteMapping("/user/{id}")
public String deleteUser(@PathVariable("id") Integer id){
System.out.println("删除用户信息-->/user/"+id+"-->delete");
return "success";
}
}
//输出结果
删除用户信息-->/user/5-->delete
SpringMVC处理AJAX请求
@RequestBody
@RequestBody可以获取请求体信息
,使用@RequestBody注解标识控制器方法的形参,当前请求的请求体就会为当前注解所标识的形参赋值
前端页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<p>首页页面</p>
<form th:action="@{/testAjax}" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<input type="submit">
</form>
</body>
</html>
后端代码
@Controller
public class TestAjax {
@RequestMapping("/")
public String index() {
//设置视图名称
return "index";
}
@PostMapping("/testAjax")
public String testRequestBody(@RequestBody String requestBody){
System.out.println("requestBody:"+requestBody);
return "success";
}
// requestBody:username=lsc&password=123456
}
- 注意我们的Get是没有请求体的,我们Get的参数传递是通过我们的url中的请求参数进行传递
@RequestBody获取json格式的请求参数
在使用了axios发送ajax请求之后,浏览器发送到服务器的请求参数有两种格式:
-
name=value&name=value…,此时的请求参数可以通过request.getParameter()获取,对应SpringMVC中,可以直接通过控制器方法的形参获取此类请求参数
-
{key:value,key:value,…},此时无法通过request.getParameter()获取,之前我们使用操作json的相关jar包gson或jackson处理此类请求参数,可以将其转换为指定的实体类对象或map集合。在SpringMVC中,直接使用@RequestBody注解标识控制器方法的形参即可将此类请求参数转换为java对象
-
3、在控制器方法的形参位置,设置json格式的请求参数要转换成的java类型(实体类或map)的参数,并使用@RequestBody注解标识
前提准备
导入jackson的依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.1</version>
</dependency>
SpringMVC的配置文件中设置开启mvc的注解驱动
<!--开启mvc的注解驱动-->
<mvc:annotation-driven />
对应前后端代码
前端代码
<body>
<div id="app">
<h1>index.html</h1>
<input type="button" @click="testRequestBodyJson1" value="测试RequestBody的JSON格式使用String接收"><br>
<input type="button" @click="testRequestBodyJson2" value="测试RequestBody的JSON格式使用Map接收"><br>
<input type="button" @click="testRequestBodyJson3" value="测试RequestBody的JSON格式使用对象接收"><br>
</div>
</body>
对应的Axios代码
<script type="text/javascript" th:src="@{js/vue.js}" ></script>
<script type="text/javascript" th:src="@{js/axios.min.js}"></script>
<script type="text/javascript">
window.onload=function () {
var vue=new Vue({
el:"#app",
data:{
username:"lsc",
password:"123"
},
methods:{
testRequestBodyJson1:function () {
axios({
method:"POST",
url:"/springmvc/testRequestBody1/json",
data: {
username: vue.username,
password: vue.password
}
}).then(function (value) {
console.log(value.data)
})
},
testRequestBodyJson2:function () {
axios.post(
"/springmvc/testRequestBody2/json",
{username:vue.username,password:vue.password}
).then(function(value){
console.log(value.data)
})
},
testRequestBodyJson3:function () {
axios.post(
"/springmvc/testRequestBody3/json",
{username:vue.username,password:vue.password}
).then(function(value){
console.log(value.data)
})
}
}
});
}
</script>
-
url表示的是请求路径
-
method表示的是请求方法的类型
- 如果是axios.post这种类型,就不要再写method属性
-
params表示的是以请求参数的方式来传递信息 ?name=value&pwd=value
- 这种方式不管我们的请求方式是post还是get,最终都会被拼接到我们url中
- 这种方式传递的参数可以通过request.getParameter来获取
-
data表示的会将参数放入到我们的请求体的请求报文中
- 会以json的格式来传递参数
- 这种方式传递的参数不可以通过request.getParameter来获取
后端代码
我们的三种方式的后端代码的方法返回值都是void,为什么呢?因为我们的ajax请求是局部刷新,也就是不会更新整个页面(不刷新浏览器窗口,不做页面跳转),所以我们的转发或者是重定向就不会生效
用字符串接收
@PostMapping("/testRequestBody1/json")
public void testRequestBodyJson1(@RequestBody String requestBody,HttpServletResponse response) throws IOException {
System.out.println("requestBody:"+requestBody);
response.getWriter().write("hello,RequestBody");
}
//输出结果
requestBody:{"username":"lsc","password":"123"}
- 我们可以看到利用axios的data传输数据是通过json格式进行传输
用Map接收
@PostMapping("/testRequestBody2/json")
public void testRequestBodyJson2(@RequestBody Map<String,Object> map, HttpServletResponse response) throws IOException {
System.out.println(map);
response.getWriter().write("hello,RequestBody");
}
//输出结果
{username=lsc, password=123}
用实体类对象接收
public class User {
private String username;
private String password;
}
@PostMapping("/testRequestBody3/json")
public void testRequestBodyJson3(@RequestBody User user, HttpServletResponse response) throws IOException {
System.out.println(user);
response.getWriter().write("hello,RequestBody");
}
//输出结果
User{username='lsc', password='123'}
@ResponseBody
@ResponseBody用于标识一个控制器方法,可以将该方法的返回值直接作为响应报文的响应体响应到浏览器
<input type="button" @click="testResponseBody" value="测试ResponseBody">
@GetMapping("/testResponseBody")
@ResponseBody
public String testResponseBody(){
return "success";
}
@GetMapping("/testResponseBody")
public String testThymeleaf(){
return "success";
}
- 对于testThymeleaf()方法,会经过thymeleaf渲染,会跳转到逻辑视图success所以对应的页面
- testResponseBody()方法,直接会响应浏览器数据success
@ResponseBody响应浏览器json数据
- 服务器处理ajax请求之后,大多数情况都需要向浏览器响应一个java对象,此时必须将java对象转换为json字符串才可以响应到浏览器
- 之前我们使用操作json数据的jar包gson或jackson将java对象转换为json字符串。
- 在SpringMVC中,我们可以直接使用@ResponseBody注解实现此功能
前提
- 导入jackson依赖
- SpringMVC配置文件中开启mvc的注解驱动
<input type="button" value="测试@ResponseBody响应浏览器json格式的数据" @click="testResponseBody"><br>
<input type="button" value="测试@ResponseBody响应浏览器json格式的数据之Map集合" @click="testResponseBody2"><br>
<input type="button" value="测试@ResponseBody响应浏览器json格式的数据之对象" @click="testResponseBody3"><br>
<script type="text/javascript">
window.οnlοad=function () {
var vue=new Vue({
el:"#app",
data:{
username:"lsc",
password:"123"
},
methods:{
testResponseBody1:function (){
axios.post(
"/springmvc/testResponseBody/json/list"
).then(function (value) {
console.log(value.data)
})
},
testResponseBody2:function (){
axios.post(
"/springmvc/testResponseBody/json/map"
).then(function (value) {
console.log(value.data)
})
},
testResponseBody3:function (){
axios.post(
"/springmvc/testResponseBody/json/class"
).then(function (value) {
console.log(value.data)
})
}
}
});
}
</script>
响应浏览器list集合
@PostMapping("/testResponseBody/json")
@ResponseBody
public List<User> testResponseBodyJson(){
User user1=new User("Lsc","123");
User user2=new User("Lsc","123");
User user3=new User("Lsc","123");
List<User> list= Arrays.asList(user1,user2,user3);
return list;
}
响应浏览器map集合
@PostMapping("/testResponseBody/json/map")
@ResponseBody
public Map<String, Object> testResponseBodyJson2(){
User user1=new User("Lsc","123");
User user2=new User("Lkx","123");
User user3=new User("Xsg","123");
Map<String,Object> map=new HashMap<>();
map.put("1",user1);
map.put("2",user2);
map.put("3",user3);
return map;
}
响应浏览器实体类对象
@RequestMapping("/testResponseBody/json/class")
@ResponseBody
public User testResponseBodyJson3(){
User user=new User("LSC","12345");
return user;
}
@RestController注解
@RestController注解是springMVC提供的一个复合注解,标识在控制器的类上,就相当于为类添加了@Controller注解,并且为其中的每个方法添加了@ResponseBody注解
文件上传和下载
文件下载
ResponseEntity用于控制器方法的返回值类型,该控制器方法的返回值就是响应到浏览器的响应报文
使用ResponseEntity实现下载文件的功能
@RequestMapping("/testDown")
public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws IOException {
//获取ServletContext对象
ServletContext servletContext = session.getServletContext();
//获取服务器中文件的真实路径
String realPath = servletContext.getRealPath("/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;
}
文件上传
文件上传要求form表单的请求方式必须为post,并且添加属性enctype=“multipart/form-data” SpringMVC中将上传的文件封装到MultipartFile对象中,通过此对象可以获取文件相关信息
前提准备
导入相关依赖
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
在SpringMVC的配置文件中添加配置
<!--必须通过文件解析器的解析才能将文件转换为MultipartFile对象-->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
</bean>
前端代码
<form th:action="@{/testUp}" method="post" enctype="multipart/form-data">
头像:<input type="file" name="photo"><br>
<input type="submit" value="上传">
</form>
后端代码
@RequestMapping("/testUp")
public String testUp(MultipartFile photo,HttpSession session) throws IOException {
//获取上传的文件名
String fileName=photo.getOriginalFilename();
System.out.println(fileName);
//解决文件重名的问题
String hzName=fileName.substring(fileName.lastIndexOf("."));
System.out.println(hzName);
fileName= UUID.randomUUID().toString()+hzName;
System.out.println(fileName);
//获取服务器中photo目录的路径
ServletContext servletContext=session.getServletContext();
String photoPath=servletContext.getRealPath("photo");
System.out.println(photoPath);
File file=new File(photoPath);
if(!file.exists()){
file.mkdir();
}
String finalPath=photoPath+File.separator+fileName;
System.out.println(finalPath);
photo.transferTo(new File(finalPath));
return "success";
}
拦截器
- SpringMVC中的拦截器用于拦截控制器方法的执行
- SpringMVC中的拦截器需要实现HandlerInterceptor
- SpringMVC的拦截器必须在SpringMVC的配置文件中进行配置:
拦截器的配置
第一种配置方式
<mvc:interceptors>
<!-- <bean class="com.lsc.springmvc.interceptor.FirstInterceptor"></bean>-->
<ref bean="firstInterceptor"></ref>
</mvc:interceptors>
- 两种bean的配置方式都可以找到对应的拦截器对象
- 对于这种方式的配置,拦截器默认会拦截所有交给DispatcherServlet的请求和响应
第二种配置方式
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/"/>
<ref bean="firstInterceptor"></ref>
</mvc:interceptor>
</mvc:interceptors>
- mvc:mapping表示这个拦截器要拦截具体的请求是什么
- /**表示任意深度的请求 比如可以是 /test /test/hello
- /*只能表示 /test 一个深度的请求
- mvc:exclude表示排除什么请求不会进行拦截
拦截器的三个方法
SpringMVC中的拦截器有三个抽象方法:
- preHandle:控制器方法执行之前执行preHandle(),
- 其boolean类型的返回值表示是否拦截或放行,返回true为放行,即调用控制器方法;返回false表示拦截,即不调用控制器方法
- postHandle:控制器方法执行之后执行postHandle()
- afterCompletion:处理完视图和模型数据,渲染视图完毕之后执行afterCompletion()
@Component
public class FirstInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("控制器方法执行之前执行 preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("控制器方法执行之后执行postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("渲染视图完毕之后执行afterCompletion");
}
}
多个拦截器的执行顺序
- 若每个拦截器的preHandle()都返回true
- 此时多个拦截器的执行顺序和拦截器在SpringMVC的配置文件的配置顺序有关:
- preHandle()会按照配置的顺序执行,而postHandle()和afterCompletion()会按照配置的反序执行
- 若某个拦截器的preHandle()返回了false
- preHandle()返回false和它之前的拦截器的preHandle()都会执行,postHandle()都不执行,返回false的拦截器之前的拦截器的afterCompletion()会执行
异常处理器
基于xml配置的异常处理
SpringMVC提供了一个处理控制器方法执行过程中所出现的异常的接口:HandlerExceptionResolver
HandlerExceptionResolver接口的实现类有:
- DefaultHandlerExceptionResolver和SimpleMappingExceptionResolver
- DefaultHandlerExceptionResolver 如果没有进行配置,那么SpringMVC默认使用这个
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="java.lang.ArithmeticException">error</prop>
</props>
</property>
<property name="exceptionAttribute" value="ex"></property>
</bean>
- exceptionMappings :key表示设置要处理的异常,value设置出现该异常是要进行跳转的页面对应的逻辑视图
- exceptionAttribute 属性设置一个属性名,将出现的异常信息在请求域中进行共享
基于注解的异常处理
@ControllerAdvice
public class ExceptionController {
@ExceptionHandler(ArithmeticException.class)
public String handleArithmeticExceptionException(Exception ex, Model model){
model.addAttribute("ex",ex);
return "error";
}
}
- @ControllerAdvice 将当前类标识为异常处理的组件
- @ExceptionHandler 用于设置所标识方法要处理的异常
- model.addAttribute();向请求域中添加数据
注解配置SpringMVC
- 之前我们前面使用了web.xml和springmvc.xml进行配置
- 我们也可以通过注解的方式来代替这两个文件的功能
创建初始化类,代替web.xml
- 在Servlet3.0环境中,容器会在类路径中查找实现javax.servlet.ServletContainerInitializer接口的类,如果找到的对应的实现类话就用它来配置Servlet容器。
- Spring提供了这个接口的实现,名为SpringServletContainerInitializer 这个类反过来又会查找实现WebApplicationInitializer的类并将配置的任务交给它们来完成。
- Spring3.2引入了一个便利的WebApplicationInitializer基础实现,名为AbstractAnnotationConfigDispatcherServletInitializer,
当我们的类扩展了AbstractAnnotationConfigDispatcherServletInitializer并将其部署到Servlet3.0容器的时候
,容器会自动发现它,并用它来配置Servlet上下文。
- Spring3.2引入了一个便利的WebApplicationInitializer基础实现,名为AbstractAnnotationConfigDispatcherServletInitializer,
- Spring提供了这个接口的实现,名为SpringServletContainerInitializer 这个类反过来又会查找实现WebApplicationInitializer的类并将配置的任务交给它们来完成。
/**
* 这个类用来代替web.xml
*/
public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* 指定spring的配置类来代替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.setForceEncoding(true);//设置响应的编码也是utf-8
//创建处理请求方式的过滤器 为了满足REST风格的编码
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
return new Filter[]{characterEncodingFilter,hiddenHttpMethodFilter};
}
}
创建SpringConfig配置类,代替spring的配置文件
@Configuration
public class SpringConfig {
//ssm整合之后,spring的配置信息写在此类中
}
创建WebConfig配置类,代替SpringMVC的配置文件
- 我们之前在SpringMVC中配置的东西
- 扫描组件——用来扫描注解标识的类是否为bean对象,被SpringIoc容器进行管理
- 视图解析器——用来进行thymeleaf的视图渲染和转发和对应的逻辑视图的前缀和后缀的添加
- 默认的servlet
- 配置默认的sevlet是为了处理静态资源 若只配置了<mvc:default-servlet-handler />,此时浏览器发送的所有请求都会被DefaultServlet处理
- mvc的注解驱动
- 在我们为了让浏览器发送的请求会先被DispatcherServlet处理,无法处理在交给DefaultServlet处理的时候需要给默认的servlet处理
- 文件上传解析器——用于文件上传时
- 拦截器——对于控制器方法进行对应的拦截处理
- 异常解析器——用于发生异常可以跳转到对应的页面
- 视图控制器——用xml的方式进行将对应的请求对应到配置逻辑视图,然后经过视图解析器到对应的页面
配置默认的servlet处理静态资源
当前工程的web.xml配置的前端控制器DispatcherServlet的url-pattern是/
tomcat的web.xml配置的DefaultServlet的url-pattern也是/
此时,浏览器发送的请求会优先被DispatcherServlet进行处理,但是DispatcherServlet无法处理静态资源
/**
* 用来代替springMVC的配置文件
*/
@Configuration//将当前类配置为配置类
//扫描组件
@ComponentScan("com.lsc.springmvc")
@EnableWebMvc()//开启MVC的注解驱动
public class WebConfig implements WebMvcConfigurer {
/**
* 默认的servlet来处理静态资源
* @param configurer
*/
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
/**
* 配置对应视图控制器
* @param registry
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
}
/**
* 配置文件上传解析器
* @return
*/
@Bean
public CommonsMultipartResolver multipartResolver(){
return new CommonsMultipartResolver();
}
/**
* 配置拦截器
* @return
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
FirstInterceptor firstInterceptor=new FirstInterceptor();
registry.addInterceptor(firstInterceptor).addPathPatterns("/**").excludePathPatterns("/");
}
/**
* 配置异常解析器
* @param resolvers
*/
@Override
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
SimpleMappingExceptionResolver simpleMappingExceptionResolver = new SimpleMappingExceptionResolver();
Properties prop=new Properties();
prop.setProperty("java.lang.ArithmeticException","error");
simpleMappingExceptionResolver.setExceptionMappings(prop);
simpleMappingExceptionResolver.setExceptionAttribute("ex");
resolvers.add(simpleMappingExceptionResolver);
}
//配置生成模板解析器
@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执行流程
SpringMVC常用组件
-
DispatcherServlet:前端控制器,不需要工程师开发,由框架提供
- 作用:统一处理请求和响应,整个流程控制的中心,由它调用其它组件处理用户的请求
-
HandlerMapping:处理器映射器,不需要工程师开发,由框架提供
- 作用:根据请求的url、method等信息查找Handler,即控制器方法 我们的requestMapping中的value属性,method属性,params等都是这个作用
- 通过扩展处理器适配器支持更多类型的处理器
-
Handler:处理器,需要工程师开发
- 作用:在DispatcherServlet的控制下Handler对具体的用户请求进行处理 就是在我们的Controller层编写的代码
-
HandlerAdapter:处理器适配器,不需要工程师开发,由框架提供
- 作用:通过HandlerAdapter对处理器(控制器方法)进行执行,我们的HandlerMapping只是找到了对应的方法,而方法的执行还是得靠HandlerAdapter来进行调用
-
ViewResolver:视图解析器,不需要工程师开发,由框架提供
- 作用:进行视图解析,得到相应的视图,例如:ThymeleafView、InternalResourceView、RedirectView
- 也就是将逻辑视图名解析为真正的视图
-
View:视图
- 作用:将模型数据通过页面展示给用户 比如我们的前端HTML页面
DispatcherServlet初始化过程
首先DispatcherServlet本质上是一个Servlet,所以天然还是遵循我们的Servlet的生命周期,所以在宏观上还是在Servlet的生命周期进行调度
对于一个Servlet默认情况下:
-
第一次接收请求时,这个Servlet会进行实例化(调用构造方法)、初始化(调用init())、然后服务(调用service())
-
从第二次请求开始,每一次都是服务
-
当容器关闭时,其中的所有的servlet实例会被销毁,调用销毁方法 destroy()
-
从调用构造方法可以看出 Servlet实例tomcat只会创建一个,所有的请求都是这个实例去响应。
-
一般情况下,第一次请求时,Servlet才会去实例化,初始化,然后再服务.这样的好处是什么? 提高系统的启动速度 。 这样的缺点是什么? 第一次请求时,耗时较长。
- 因此得出结论: 如果需要提高系统的启动速度,当前默认情况就是这样。如果需要提高响应速度,我们应该设置Servlet的初始化时机。
<!--
作为框架的核心组件,在启动过程中有大量的初始化操作要做
而这些操作放在第一次请求时才执行会严重影响访问速度
因此需要通过此标签将启动控制DispatcherServlet的初始化时间提前到服务器启动时
-->
<load-on-startup>1</load-on-startup>
因此我们的看DispatcherServlet初始化必须关注它的init方法
DispatcherServlet的继承关系
GenericServlet的init方法
//这是重写Servlet接口的void init(ServletConfig var1) throws ServletException
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
//这是重载init方法
public void init() throws ServletException {
}
- 这里的意义就是在执行void init(ServletConfig config),也会调用了 void init() ,而且给成员对象 config赋值
HttpServlet的init方法
- 没有重写或者重载init方法,所以完全继承GenericServlet的init方法
HttpServletBean的init方法
//这是重写了GenericServlet的 public void init() throws ServletException方法
public final void init() throws ServletException {
PropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment()));
this.initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
} catch (BeansException var4) {
if (this.logger.isErrorEnabled()) {
this.logger.error("Failed to set bean properties on servlet '" + this.getServletName() + "'", var4);
}
throw var4;
}
}
this.initServletBean();
}
- this.initServletBean()这里调用了initServletBean()方法,把初始化的功能交给了其它的方法,也就是我们的initServletBean
HttpServletBean的initServletBean方法
protected void initServletBean() throws ServletException {
}
- HttpServletBean中这个方法是空的,而且还是protected,明显是让子类重写去实现
FrameworkServlet的init方法
没有重写或者重载init方法,所以完全继承HttpServletBean的init方法
FrameworkServlet的initServletBean方法
protected final void initServletBean() throws ServletException {
this.getServletContext().log("Initializing Spring " + this.getClass().getSimpleName() + " '" + this.getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("Initializing Servlet '" + this.getServletName() + "'");
}
long startTime = System.currentTimeMillis();
try {
this.webApplicationContext = this.initWebApplicationContext();
this.initFrameworkServlet();
} catch (RuntimeException | ServletException var4) {
this.logger.error("Context initialization failed", var4);
throw var4;
}
if (this.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";
this.logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails + "': request parameters and headers will be " + value);
}
if (this.logger.isInfoEnabled()) {
this.logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
}
}
- this.webApplicationContext = this.initWebApplicationContext()进行实现初始化WebApplicationContext(初始化Web Ioc容器)
- 这里也可以证SpringMVC配置文件是在DispatcherServlet初始化的时候完成的
- this.initFrameworkServlet() 是空的,DispatcherServlet中没有重写,所以就不关注
初始化WebApplicationContext
org.springframework.web.servlet.FrameworkServlet
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
WebApplicationContext wac = null;
//先进行判断Web Ioc容器是否为null
if (this.webApplicationContext != null) {
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
this.configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
//如果Web Ioc容器为null,先进行寻找操作
wac = this.findWebApplicationContext();
}
if (wac == null) {
//如果没有找到,就创建一个新的Ioc容器
wac = this.createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
synchronized(this.onRefreshMonitor) {
this.onRefresh(wac);//进行刷新容器
}
}
if (this.publishContext) {
String attrName = this.getServletContextAttributeName();
//将SpringMVCIoc容器共享到我们Servlet的上下文,也就是application(应用)域对象中
this.getServletContext().setAttribute(attrName, wac);
}
return wac;
}
创建WebApplicationContext
org.springframework.web.servlet.FrameworkServlet
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
Class<?> contextClass = this.getContextClass();
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Fatal initialization error in servlet with name '" + this.getServletName()
+ "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext");
} else {
// 通过反射创建 IOC 容器对象
ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(this.getEnvironment());
// 设置父容器 Spring 的Ioc容器是SpringMVC Ioc容器的父容器
wac.setParent(parent);
String configLocation = this.getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
this.configureAndRefreshWebApplicationContext(wac);
return wac;
}
DispatcherServlet初始化策
FrameworkServlet创建WebApplicationContext后,刷新容器,调用onRefresh(wac),此方法在DispatcherServlet中进行了重写,调用了initStrategies(context)方法,初始化策略,即初始化DispatcherServlet的各个组件
protected void onRefresh(ApplicationContext context) {
this.initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
this.initMultipartResolver(context);//初始化文件上传解析器
this.initLocaleResolver(context);//初始化本地解析器
this.initThemeResolver(context);
this.initHandlerMappings(context);//初始化处理器映射器
this.initHandlerAdapters(context);//初始化处理器适配器
this.initHandlerExceptionResolvers(context);//初始化异常解析器
this.initRequestToViewNameTranslator(context);
this.initViewResolvers(context);//初始化视图解析器
this.initFlashMapManager(context);
}
DispatcherServlet调用组件处理请求
我们知道Servlet处理请求的通过调用Service方法,所以我们需要关注Service方法
GenericServlet的service方法
public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
- 我们GenreicServlet中将service方法设置为了抽象方法
HttpServlet的service方法
//这是重写了GenericServlet的service方法
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
if (req instanceof HttpServletRequest && res instanceof HttpServletResponse) {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
this.service(request, response);
} else {
throw new ServletException("non-HTTP request or response");
}
}
//这是重载了servlet方法,对应的是我们Http的请求和响应
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
long lastModified;
if (method.equals("GET")) {
lastModified = this.getLastModified(req);
if (lastModified == -1L) {
this.doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader("If-Modified-Since");
if (ifModifiedSince < lastModified) {
this.maybeSetLastModified(resp, lastModified);
this.doGet(req, resp);
} else {
resp.setStatus(304);
}
}
} else if (method.equals("HEAD")) {
lastModified = this.getLastModified(req);
this.maybeSetLastModified(resp, lastModified);
this.doHead(req, resp);
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}
}
-
String method = req.getMethod(); 这个方法是获取请求的方式
- 然后通过后面的if和else if的结构来通过什么类型的方法去执行不同的逻辑
- 决定调用的是哪个do开头的方法
- 然后通过后面的if和else if的结构来通过什么类型的方法去执行不同的逻辑
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_post_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(405, msg);
} else {
resp.sendError(400, msg);
}
}
在HttpServlet这个抽象类中,do方法都差不多
- 那么在HttpServlet中这些do方法默认都是405的实现风格-要我们子类去实现对应的方法,否则默认会报405错误
HttpServletBean的service方法
没有进行重写,所以完全继承HttpServlet
FrameworkServlet的service方法
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());//获取请求方法的类型
if (httpMethod != HttpMethod.PATCH && httpMethod != null) {
super.service(request, response);
} else {
this.processRequest(request, response);
}
}
- 有一个if else结果,如果满足(httpMethod != HttpMethod.PATCH && httpMethod != null)
- 如果满足则调用父类的service,也就是HttpServlet的service方法
- 不满足,则调用processRequest()方法
- 对应调用父类service方法,但是我们FrameworkServlet重写了doXXX()方法,所以service中调用的还是我们FrameworkServlet重写的doxxx方法
protected final void doGet(HttpServletRequest request, HttpServletResponse response) {
this.processRequest(request, response);
}
FrameworkServlet重写HttpServlet中的service()和doXxx(),这些方法中调用了processRequest(request, response)
processRequest方法
protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = this.buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = this.buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new FrameworkServlet.RequestBindingInterceptor());
this.initContextHolders(request, localeContext, requestAttributes);
try {
this.doService(request, response);
} catch (IOException | ServletException var16) {
failureCause = var16;
throw var16;
} catch (Throwable var17) {
failureCause = var17;
throw new NestedServletException("Request processing failed", var17);
} finally {
this.resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
this.logResult(request, response, (Throwable)failureCause, asyncManager);
this.publishRequestHandledEvent(request, response, startTime, (Throwable)failureCause);
}
}
- this.doService(request, response);我们的processRequest中通过调用doService方法处理请求
- 在FrameworkServlet中的doService方法是抽象方法,由子类进行重写
DispatcherServlet的doService方法和doDispatch
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
this.logRequest(request);
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap();
Enumeration attrNames = request.getAttributeNames();
label120:
while(true) {
String attrName;
do {
if (!attrNames.hasMoreElements()) {
break label120;
}
attrName = (String)attrNames.nextElement();
} while(!this.cleanupAfterInclude && !attrName.startsWith("org.springframework.web.servlet"));
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.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 {
this.doDispatch(request, response);
} finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {
this.restoreAttributesAfterInclude(request, attributesSnapshot);
}
if (requestPath != null) {
ServletRequestPathUtils.clearParsedRequestPath(request);
}
}
}
- this.doDispatch(request, response);处理由我们doDispatch进行处理
doDispatch
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
try {
ModelAndView mv = null;
Object dispatchException = null;
try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
/*
mappedHandler:调用链包含handler、interceptorList、interceptorIndex
handler:浏览器发送的请求所匹配的控制器方法
interceptorList:处理控制器方法的所有拦截器集合
interceptorIndex:拦截器索引,控制拦截器afterCompletion()的执行
*/
mappedHandler = this.getHandler(processedRequest);//创建对应的HandlerMapping对象
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
// 通过控制器方法创建相应的处理器适配器,调用所对应的控制器方法
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());//创建了HandlerAdapter对象
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;
}
// 由处理器适配器调用具体的控制器方法,最终获得ModelAndView对象
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
this.applyDefaultViewName(processedRequest, mv);
//然后调用调用拦截器PostHandle方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var20) {
dispatchException = var20;
} catch (Throwable var21) {
dispatchException = new NestedServletException("Handler dispatch failed", var21);
}
//后续处理:处理模型数据和渲染视图也就是我们的ModeAndView
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
//在这个方法中,执行完渲染,就会调用拦截器的afterCompletion方法
} catch (Exception var22) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
} catch (Throwable var23) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
}
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else if (multipartRequestParsed) {
this.cleanupMultipart(processedRequest);
}
}
}
processDispatchResult
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) {
this.logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException)exception).getModelAndView();
} else {
Object handler = mappedHandler != null ? mappedHandler.getHandler() : null;
mv = this.processHandlerException(request, response, handler, exception);
errorView = mv != null;
}
}
if (mv != null && !mv.wasCleared()) {
this.render(mv, request, response);// 处理模型数据和渲染视图
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
} else if (this.logger.isTraceEnabled()) {
this.logger.trace("No view rendering, null ModelAndView returned.");
}
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
// 调用拦截器的afterCompletion()
mappedHandler.triggerAfterCompletion(request, response, (Exception)null);
}
}
}
SpringMVC的执行流程
-
用户向服务器发送请求,请求被SpringMVC 前端控制器 DispatcherServlet捕获。(通过HTTP服务器和Servelt容器的功劳)
-
DispatcherServlet调用HandlerMapper对请求URL进行解析,得到请求资源标识符(URI),判断请求URI对应的映射:
- 不存在 则会报404错误
- 对应的资源存在 根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain执行链对象的形式返回。
-
DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter用来调用对应的Handler方法。
- 这里的Handler也就是代指我们的HandlerExecutionChain
- 为什么需要适配器,因为我们的handler有很多种实现方式,所以使用适配器模式,为了选择最合适的形参和对应调用方法(by反射做到的)
-
通过适配器去调用HandlerExecutionChain执行链对象——视为调用到我们自己写的方法上
-
此时将开始执行拦截器的preHandler(…)方法【正向】
-
提取Request中的模型数据,填充Handler入参(也就是将request域对象中的数据填充到我们调用的方法的参数中),开始执行Handler(Controller)方法,处理请求。
-
在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:
a) HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息
b) 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等
c) 数据格式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等
d) 数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中
-
-
-
如果我们的方法没有通过@ResponseBody修饰,Controller方法执行完成后,向DispatcherServlet 返回一个ModelAndView对象。
-
此时将开始执行拦截器的postHandle(…)方法【逆向】。
-
DispathcerServlet 根据返回的ModelAndView(此时会判断是否存在异常:如果存在异常,则执行HandlerExceptionResolver进行异常处理)选择一个适合的ViewResolver进行视图解析,根据Model和View,来渲染视图。得到对应的视图
-
渲染视图完毕执行拦截器的afterCompletion(…)方法【逆向】。
-
将渲染结果返回给客户端。将相应内容填充到HttpServletResponse对象(Servlet API的要求,自然变成了HTTP响应返回给浏览器)
对于URI不存在的情况
- 判断是否配置了mvc:default-servlet-handler ,也就是我们的TomCat默认配置的Servlet
- 如果没配置,则控制台报映射查找不到,客户端展示404错误
- 如果有配置,则访问目标资源(一般为静态资源,如:JS,CSS,HTML),找不到客户端也会展示404错误