diff --git a/docs/JUC/JUC.md b/docs/JUC/JUC.md index fc9f03f..d9ceb42 100644 --- a/docs/JUC/JUC.md +++ b/docs/JUC/JUC.md @@ -841,6 +841,8 @@ ThreadLocal,即线程变量,是一个以 ThreadLocal 对象为键、任意 队列同步器 AbstractQueuedSynchronizer,是用来构建锁或者其他同步组件的基础框架,它使用了一个 int 成员变量表示同步状态,通过内置的 FIFO 队列来完成资源获取线程的排队工作。 +AQS核心思想是,如果被请求的共享资源空闲,那么就将当前请求资源的线程设置为有效的工作线程,将共享资源设置为锁定状态;如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配,将暂时获取不到锁的线程加入到队列中。 + ```java public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer{ // 指向同步队列的头部 @@ -2285,7 +2287,13 @@ Java8 中的 `ConcurrentHashMap` 使用的 `Synchronized` 锁加 CAS 的机制 ### CAS -CAS 的全称是 Compare-And-Swap,依赖于CPU的原子性指令实现。在 Java 中,实现 CAS 操作的一个关键类是`Unsafe`。 +CAS 的全称是 Compare-And-Swap,依赖于CPU的原子性指令实现。在 Java 中,实现 CAS 操作的一个关键类是`Unsafe`。它包含三个操作数: + +1. 变量内存地址,V表示 +2. 旧的预期值,A表示 +3. 准备设置的新值,B表示 + +当执行CAS指令时,只有当V等于A时,才会用B去更新V的值,否则就不会执行更新操作。 diff --git a/docs/MySQL/MySQL.md b/docs/MySQL/MySQL.md index 7c4b2cd..86e7903 100644 --- a/docs/MySQL/MySQL.md +++ b/docs/MySQL/MySQL.md @@ -1442,12 +1442,21 @@ show profiles 能够在做SQL优化时帮助我们了解时间都耗费到哪里 EXPLAIN 或者 DESC命令获取 MySQL 如何执行 SELECT 语句的信息,包括在 SELECT 语句执行过程中,表如何连接和连接的顺序。 -`EXPLAIN` 并不会真的去执行相关的语句,而是通过 查询优化器 对语句进行分析,找出最优的查询方案,并显示对应的信息。 +`EXPLAIN` 并不会真的去执行相关的语句,而是通过查询优化器对语句进行分析,找出最优的查询方案,并显示对应的信息。 语法 :直接在 select 语句之前加上关键字 explain/desc; ![](MySQL\explain.webp) +type 字段就是描述了找到所需数据时使用的扫描方式是什么,常见扫描类型的执行效率从低到高的顺序为: + +- All(全表扫描):在这些情况里,all 是最坏的情况,因为采用了全表扫描的方式。 +- index(全索引扫描):index 和 all 差不多,只不过 index 对索引表进行全扫描,这样做的好处是不再需要对数据进行排序,但是开销依然很大。所以,要尽量避免全表扫描和全索引扫描。 +- range(索引范围扫描):range 表示采用了索引范围扫描,一般在 where 子句中使用 < 、>、in、between 等关键词,只检索给定范围的行,属于范围查找。**从这一级别开始,索引的作用会越来越明显,因此我们需要尽量让 SQL 查询可以使用到 range 这一级别及以上的 type 访问方式**。 +- ref(非唯一索引扫描):ref 类型表示采用了非唯一索引,或者是唯一索引的非唯一性前缀,返回数据返回可能是多条。因为虽然使用了索引,但该索引列的值并不唯一,有重复。这样即使使用索引快速查找到了第一条数据,仍然不能停止,要进行目标值附近的小范围扫描。但它的好处是它并不需要扫全表,因为索引是有序的,即便有重复值,也是在一个非常小的范围内扫描。 +- eq_ref(唯一索引扫描):eq_ref 类型是使用主键或唯一索引时产生的访问方式,通常使用在多表联查中。比如,对两张表进行联查,关联条件是两张表的 user_id 相等,且 user_id 是唯一索引,那么使用 EXPLAIN 进行执行计划查看的时候,type 就会显示 eq_ref。 +- const(结果只有一条的主键或唯一索引扫描):const 类型表示使用了主键或者唯一索引与常量值进行比较,比如 select name from product where id=1。需要说明的是 const 类型和 eq_ref 都使用了主键或唯一索引,不过这两个类型有所区别,**const 是与常量进行比较,查询效率会更快,而 eq_ref 通常用于多表联查中**。 + extra 几个重要的参考指标: - Using filesort :当查询语句中包含 group by 操作,而且无法利用索引完成排序操作的时候, 这时不得不选择相应的排序算法进行,甚至可能会通过文件排序,效率是很低的,所以要避免这种问题的出现。 diff --git "a/docs/Redis/Redis\345\237\272\347\241\200.md" "b/docs/Redis/Redis\345\237\272\347\241\200.md" index b4cac6b..3cc7b2f 100644 --- "a/docs/Redis/Redis\345\237\272\347\241\200.md" +++ "b/docs/Redis/Redis\345\237\272\347\241\200.md" @@ -270,10 +270,10 @@ RDB保存的是dump.rdb文件。 **优势**: -1. 适合大规模的数据恢复; -2. 按照业务定时备份; -3. 父进程不会执行磁盘I/О或类似操作,会fork一个子进程完成持久化工作,性能高; -4. RDB文件在内存中的加载速度要比AOF快很多。 +1. RDB通过快照的形式保存某一时刻的数据状态,文件体积小; +2. 备份和恢复的速度非常快; +3. RDB是在主线程之外通过fork子进程来进行的,不会阻塞服务器处理命令请求; +4. RDB文件通常比AOF文件小得多。 **劣势**: @@ -355,8 +355,8 @@ AOF保存的是 appendonly.aof 文件 2. AOF缓冲会根据AOF缓冲区同步文件的三种写回策略将命令写入磁盘上的AOF文件。 1. **ALways**:同步写回,每个写命令执行完立刻同步地将日志写回磁盘。 - 2. **everysec**(默认):每秒写回,每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,每隔1秒把缓冲区中的内容写入到磁盘 - 3. **no**:操作系统控制的写回,每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘 + 2. **everysec**(默认):每秒写回,每个写命令执行完,只是先把日志写到AOF文件的内核缓冲区,每隔1秒把缓冲区中的内容写入到磁盘 + 3. **no**:操作系统控制的写回,每个写命令执行完,只是先把日志写到AOF文件的内核缓冲区,由操作系统决定何时将缓冲区内容写回磁盘 3. 随着写入AOF内容的增加为避免文件膨胀,会根据规则进行命令的合并(**AOF重写**),从而起到AOF文件压缩的目的。 diff --git "a/docs/Redis/Redis\351\253\230\347\272\247.md" "b/docs/Redis/Redis\351\253\230\347\272\247.md" index 5086f9e..0037dcc 100644 --- "a/docs/Redis/Redis\351\253\230\347\272\247.md" +++ "b/docs/Redis/Redis\351\253\230\347\272\247.md" @@ -2,9 +2,9 @@ ### Redis单线程 -redis单线程主要是指Redis的**网络IO和键值对读写是由一个线程来完成**的,Redis在处理客户端的请求时包括**获取 (socket 读)、解析、执行、内容返回 (socket 写**) 等都由一个顺序串行的主线程处理,这就是所谓的“单线程”。这也是Redis对外提供键值存储服务的主要流程。 +**Redis 单线程指的是「接收客户端请求->解析请求 ->进行数据读写等操作->发送数据给客户端」这个过程是由一个线程(主线程)来完成的**。 -但Redis的其他功能,比如**关闭文件、AOF 刷盘、释放内存**等,其实是由额外的线程执行的。 +但Redis的其他功能,比如**关闭文件、AOF 刷盘、释放内存**等,其实是由额外的线程执行的。例如执行 unlink key / flushdb async/ flushall async 等命令,会把这些删除操作交给后台线程来执行,好处是不会导致 Redis 主线程卡顿。 **Redis命令工作线程是单线程的,但是,整个Redis来说,是多线程的;** @@ -21,7 +21,7 @@ redis单线程主要是指Redis的**网络IO和键值对读写是由一个线程 1. **基于内存操作**: Redis 的所有数据都存在内存中,因此所有的运算都是内存级别的,所以他的性能比较高; 2. **数据结构简单**: Redis 的数据结构是专门设计的,而这些简单的数据结构的查找和操作的时间大部分复杂度都是 0(1),因此性能比较高; 3. Redis **采用单线程模型可以避免了多线程之间的竞争**,省去了多线程切换带来的时间和性能上的开销,而且也不会导致死锁问题。 -4. **多路复用和非阻塞 I/O**: Redis使用 I/O多路复用功能来监听多个 socket连接客户端,这样就可以使用一个线程连接来处理多个请求,减少线程切换带来的开销,同时也避免了 I/O 阻塞操作; +4. **多路复用和非阻塞 I/O**: Redis使用 I/O多路复用功能来监听多个 socket连接客户端,这样就可以使用一个线程来检查多个 Socket 的就绪状态,在单个线程中通过记录跟踪每一个 socket(I/O流)的状态来管理处理多个 I/O 流,减少线程切换带来的开销,同时也避免了 I/O 阻塞操作; diff --git a/docs/Spring/Spring6.md b/docs/Spring/Spring6.md index 2be7138..a19c581 100644 --- a/docs/Spring/Spring6.md +++ b/docs/Spring/Spring6.md @@ -1,3 +1,8 @@ +--- +hide: + - navigation +--- + ## IoC、DI和AOP思想提出 **IoC 控制反转思想的提出**: @@ -1742,3 +1747,17 @@ public class ApplicationContextConfig { 5. spring的事务在抛异常的时候会回滚,如果是catch捕获了,事务无效。可以在catch里面加上throw new RuntimeException(); 6. 和锁同时使用需要注意:由于Spring事务是通过AOP实现的,所以在方法执行之前会有开启事务,之后会有提交事务逻辑。而synchronized代码块执行是在事务之内执行的,可以推断在synchronized代码块执行完时,事务还未提交,其他线程进入synchronized代码块后,读取的数据不是最新的。 所以必须使synchronized锁的范围大于事务控制的范围,把synchronized加到Controller层或者大于事务边界的调用层! + + + +**Spring事务如果没有回滚可能是什么原因?** + +1. 非 public 修饰的方法中的事务不自动回滚,解决方案是将方法的权限修饰符改为 public 即可。 + +2. 当 @Transactional 遇上 try/catch 事务不自动回滚,解决方案有两种:一种是在 catch 中将异常重新抛出去,另一种是使用代码手动将事务回滚。 + +3. 调用类内部的 @Transactional 方法事务不自动回滚,解决方案是给调用的方法上也加上 @Transactional + +4. 抛出检查异常时事务不自动回滚,解决方案是给 @Transactional 注解上,添加 rollbackFor 参数并设置 Exception.class 值即可 + +5. 数据库不支持事务,事务也不会自动回滚,比如 MySQL 中设置了使用 MyISAM 引擎,因为它本身是不支持事务的,这种情况下,即使在程序中添加了 @Transactional 注解,那么依然不会有事务的行为,也就不会执行事务的自动回滚了。解决方式就是将 MySQL 的存储引擎设置为 InnoDB,InnoDB支持事务。 diff --git a/docs/SpringMVC/SpringMVC.md b/docs/SpringMVC/SpringMVC.md new file mode 100644 index 0000000..b38d652 --- /dev/null +++ b/docs/SpringMVC/SpringMVC.md @@ -0,0 +1,1268 @@ +--- +hide: + - navigation +--- +## SpringMVC简介 + +[SpringMVC ](https://docs.spring.io/spring-framework/reference/web/webmvc.html?spm=wolai.workspace.0.0.78d5767bUcXVsM) + +MVC 是模型(Model)、视图(View)、控制器(Controller)的简写,其核心思想是通过将业务逻辑、数据、显示分离来组织代码。 + +M:Model,模型层,指工程中的JavaBean,作用是处理数据 + +JavaBean分为两类: + +- 一类称为实体类Bean:专门存储业务数据的,如 Student、User 等 +- 一类称为业务处理 Bean:指 Service 或 Dao 对象,专门用于处理业务逻辑和数据访问。 + +V:View,视图层,指工程中的html或jsp等页面,作用是与用户进行交互,展示数据 + +C:Controller,控制层,指工程中的servlet,作用是接收请求和响应浏览器 + +MVC的工作流程: 用户通过视图层发送请求到服务器,在服务器中请求被Controller接收,Controller调用相应的Model层处理请求,处理完毕将结果返回到Controller,Controller再根据请求处理的结果找到相应的View视图,渲染数据后最终响应给浏览器 + + + +SpringMVC是一个基于Spring开发的MVC轻量级框架,Spring3.0后发布的组件,SpringMVC和Spring可以无缝整合,使用DispatcherServlet作为前端控制器,且内部提供了处理器映射器、处理器适配器、视图解析器等组件,可以简化JavaBean封装,Json转化、文件上传等操作。 + +![](./SpringMVC/SpringMVC.jpeg) + +1. 导入Spring整合SpringMVC的坐标:spring-webmvc + +2. 在web.xml中配置SpringMVC的前端控制器ServletDispatcher + + ```xml + + DispatcherServlet + org.springframework.web.servlet.DispatcherServlet + + + contextConfigLocation + classpath:spring-mvc.xml + + + 2 + + + DispatcherServlet + / + + ``` + +3. 编写一个控制器Controller,配置映射信息 + + ```java + @Controller + public class UserController { + @RequestMapping("/show") + public String show(){ + System.out.println("show 执行...."); + //视图跳转到index.jsp + return "/index.jsp"; + } + } + ``` + +4. 创建springMVC的核心配置文件 spring-mvc.xml,并配置组件扫描web层 + + ```xml + + ``` + + + +SpringMVC关键组件 + +| 组件 | 描述 | 常用组件 | +| ---------------------------- | ------------------------------------------------------------ | ---------------------------- | +| 处理器映射器:HandlerMapping | 匹配映射路径对应的Handler,返回可执行的处理器链对象HandlerExecutionChain对象 | RequestMappingHandlerMapping | +| 处理器适配器:HandlerAdapter | 匹配HandlerExecutionChain对应的适配器进行处理器调用,返回视图模型对象 | RequestMappingHandlerAdapter | +| 视图解析器:ViewResolver | 对视图模型对象进行解析 | InternalResourceViewResolver | + +![](./SpringMVC/组件关系.jpeg) + +SpringMVC的默认组件,SpringMVC 在前端控制器 DispatcherServlet加载时,就会进行初始化操作,在进行初始化时,就会加载SpringMVC默认指定的一些组件,这些默认组件配置在 DispatcherServlet.properties 文件中,该文 件存在与spring-webmvc-5.3.7.jar包下的 org\springframework\web\servlet\DispatcherServlet.properties + +这些默认的组件是在DispatcherServlet中进行初始化加载的,在DispatcherServlet中存在集合存储着这些组件, SpringMVC的默认组件会在 DispatcherServlet 中进行维护,但是并没有存储在与SpringMVC的容器中 + +当我们在Spring容器中配置了HandlerMapping,则就不会在加载默认的HandlerMapping策略了,原理比较简单,DispatcherServlet 在进行HandlerMapping初始化时,先从SpringMVC容器中找是否存在HandlerMapping,如果存在直接取出容器中的HandlerMapping,在存储到 DispatcherServlet 中的handlerMappings集合中去。 + +## SpringMVC的请求处理 + +### 请求映射路径的配置 + +配置映射路径,映射器处理器才能找到Controller的方法资源 + +| 相关注解 | 作用 | 使用位置 | +| --------------- | ---------------------------------------------- | ---------- | +| @RequestMapping | 设置控制器方法的访问资源路径,可以接收任何请求 | 方法和类上 | +| @GetMapping | 设置控制器方法的访问资源路径,可以接收GET请求 | 方法和类上 | +| @PostMapping | 设置控制器方法的访问资源路径,可以接收POST请求 | 方法和类上 | + +@RequestMapping注解: + +```java +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Mapping +@Reflective({ControllerMappingReflectiveProcessor.class}) +public @interface RequestMapping { + String name() default ""; + + @AliasFor("path") + String[] value() default {}; + + @AliasFor("value") + String[] path() default {}; + + RequestMethod[] method() default {}; + + String[] params() default {}; + + String[] headers() default {}; + + String[] consumes() default {}; + + String[] produces() default {}; +} +``` + +**value属性** + +@RequestMapping注解的value属性通过请求的请求地址匹配请求映射 + +@RequestMapping注解的value属性是一个字符串类型的数组,表示该请求映射能够匹配多个请求地址所对应的请求 + +@RequestMapping注解的value属性必须设置,至少通过请求地址匹配请求映射 + +**method属性** + +HTTP 协议定义了八种请求方式,在 SpringMVC 中封装到了下面这个枚举类: + +```java +public enum RequestMethod { + GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE +} +``` + +@RequestMapping注解的method属性通过**请求的请求方式**(get或post)匹配请求映射。默认任何请求方式都可以访问 + +@RequestMapping注解的method属性是一个RequestMethod类型的数组,表示该请求映射能够匹配多种请求方式的请求 + +若当前请求的请求地址满足请求映射的value属性,但是请求方式不满足method属性,则浏览器报错405:Request method ‘POST’ not supported + +**注:** + +1、对于处理指定请求方式的控制器方法,SpringMVC中提供了@RequestMapping的派生注解 + +处理get请求的映射–>@GetMapping + +处理post请求的映射–>@PostMapping + +处理put请求的映射–>@PutMapping + +处理delete请求的映射–>@DeleteMapping + +2、常用的请求方式有get,post,put,delete + +但是目前浏览器只支持get和post,若在form表单提交时,为method设置了其他请求方式的字符串(put或delete),则按照默认的请求方式get处理 + +若要发送put和delete请求,则需要通过spring提供的过滤器HiddenHttpMethodFilter + +**param属性** + +@RequestMapping注解的params属性通过请求的请求参数匹配请求映射 + +@RequestMapping注解的params属性是一个字符串类型的数组,可以通过四种表达式设置请求参数和请求映射的匹配关系: + +1. “param”:要求请求映射所匹配的请求必须携带param请求参数 + +2. “!param”:要求请求映射所匹配的请求必须不能携带param请求参数 + +3. “param=value”:要求请求映射所匹配的请求必须携带param请求参数且param=value + +4. “param!=value”:要求请求映射所匹配的请求必须携带param请求参数但是param!=value + +**headers属性** + +@RequestMapping注解的headers属性通过请求的请求头信息匹配请求映射 + +@RequestMapping注解的headers属性是一个字符串类型的数组,可以通过四种表达式设置请求头信息和请求映射的匹配关系 + +1. “header”:要求请求映射所匹配的请求必须携带header请求头信息 + +2. “!header”:要求请求映射所匹配的请求必须不能携带header请求头信息 + +3. “header=value”:要求请求映射所匹配的请求必须携带header请求头信息且header=value + +4. “header!=value”:要求请求映射所匹配的请求必须携带header请求头信息且header!=value + +若当前请求满足@RequestMapping注解的value和method属性,但是不满足headers属性,此时页面显示404错误,即资源未找到 + + + +### 请求数据的接收 + +#### param参数接收 + +**1. 直接接收** + +只要形参数名和类型与传递参数相同,即可自动接收! + +```java +@Controller +@RequestMapping("/param") +public class ParamController { + + /** + * 前端请求: http://localhost:8080/param/value?name=xx&age=18 + * + * 可以利用形参列表,直接接收前端传递的param参数! + * 要求: 参数名 = 形参名 + * 类型相同 + * 出现乱码正常,json接收具体解决!! + * @return 返回前端数据 + */ + @GetMapping(value="/value") + @ResponseBody + public String setupForm(String name,int age){ + System.out.println("name = " + name + ", age = " + age); + return name + age; + } +} +``` + +**2. @RequestParam注解** + +- 指定绑定的请求参数名 +- 要求请求参数必须传递 +- 为请求参数提供默认值 + +默认情况下,使用此注解的方法参数是必需的,但可以通过将 @RequestParam 注解的 required 标志设置为 false! + +```java + /** + * 前端请求: http://localhost:8080/param/data?name=xx&stuAge=18 + * + * 使用@RequestParam注解标记handler方法的形参 + * 指定形参对应的请求参数@RequestParam(请求参数名称) + */ +@GetMapping(value="/data") +@ResponseBody +public Object paramForm(@RequestParam("name") String name, + @RequestParam(value = "stuAge",required = false,defaultValue = "18") int age){ + System.out.println("name = " + name + ", age = " + age); + return name+age; +} +``` + +**3. 接收数组或集合数据** + +接收数组、集合、Map数据,客户端传递多个同名参数时,可以使用集合接收,但是集合和map需要使用@RequestParam告知框架传递的参数是要同名设置的,不是对象属性设置的。 + +```java +/** + * 前端请求: http://localhost:8080/param/mul?hbs=吃&hbs=喝 + * + * 一名多值,可以使用集合接收即可!但是需要使用@RequestParam注解指定 + */ +@GetMapping(value="/mul") +@ResponseBody +public Object mulForm(@RequestParam List hbs){ + System.out.println("hbs = " + hbs); + return hbs; +} +``` + +**4. 实体接收** + +接收实体JavaBean属性数据,单个JavaBean数据:提交的参数名称只要与Java的属性名一致,就可以进行自动封装。 + +```java +public class User { + private String username; + private Integer age; + private String[] hobbies; + private Date birthday; + private Address address; + //... 省略get和set方法 ... +} +``` + +```java +@Controller +@RequestMapping("/param") +public class ParamController { + + // 前端请求: http://localhost:8080/param/user?username=haohao&age=35&hobbies=eat&hobbies=sleep + @RequestMapping("/user") + @ResponseBody + public String addUser(User user) { + // 在这里可以使用 user 对象的属性来接收请求参数 + System.out.println("user = " + user); + return "success"; + } +} +``` + +接收实体JavaBean属性数据,嵌套JavaBean数据:提交的参数名称用 `.` 去描述嵌套对象的属性关系即可。`username=haohao&address.city=tianjin&address.area=jinghai` + +#### 路径参数接收 + +`@PathVariable` 注解允许将 URL 中的占位符映射到控制器方法中的参数。 + +例如,如果我们想将 `/user/{id}` 路径下的 `{id}` 映射到控制器方法的一个参数中,则可以使用 `@PathVariable` 注解来实现。 + +```java + /** + * 动态路径设计: /user/{动态部分}/{动态部分} 动态部分使用{}包含即可! {}内部动态标识! + * 形参列表取值: @PathVariable Long id 如果形参名 = {动态标识} 自动赋值! + * @PathVariable("动态标识") Long id 如果形参名 != {动态标识} 可以通过指定动态标识赋值! + * + * 访问测试: /param/user/1/root -> id = 1 uname = root + */ +@GetMapping("/user/{id}/{name}") +@ResponseBody +public String getUser(@PathVariable Long id, + @PathVariable("name") String uname) { + System.out.println("id = " + id + ", uname = " + uname); + return "user_detail"; +} +``` + + + +#### json参数接收 + +接收Json数据格式数据,Json数据都是以请求体的方式提交的,且不是原始的键值对格式的,所以我们要使用 @RequestBody注解整体接收该数据。 + +@RequestBody 注解表示当前方法参数的值应该从请求体中获取,并且需要指定 value 属性来指示请求体应该映射到哪个参数上。 + +```java +@PostMapping("/person") +@ResponseBody +public String addPerson(@RequestBody Person person) { + + // 在这里可以使用 person 对象来操作 JSON 数据中包含的属性 + return "success"; +} +``` + +使用Json工具( jackson )将Json格式的字符串转化为JavaBean进行操作: + +1. 加入依赖 + + ```xml + + com.fasterxml.jackson.core + jackson-databind + 2.15.0 + + ``` + +2. 配置RequestMappingHandlerAdapter,指定消息转换器,就不用手动转换json格式字符串了 + + ```xml + + + + + + + + ``` + + +#### 文件接收 + +接收文件上传的数据,文件上传的表单需要一定的要求,如下: + +* 表单的提交方式必须是POST +* 表单的enctype属性必须是multipart/form-data +* 文件上传项需要有name属性 + +```xml +
+ +
+``` + +服务器端使用MultipartFile类型接收上传文件,如果进行多文件上传的话,则使用MultipartFile数组即可: + +```java +@PostMapping("/fileUpload") +public String fileUpload(@RequestBody MultipartFile myFile) throws IOException { + System.out.println(myFile); + //获得上传的文件的流对象 + InputStream inputStream = myFile.getInputStream(); + //使用commons-io存储到C:\haohao\abc.txt位置 + FileOutputStream outputStream = new + FileOutputStream("C:\\Users\\haohao\\"+myFile.getOriginalFilename()); + IOUtils.copy(inputStream,outputStream); + //关闭资源 + inputStream.close(); + outputStream.close(); + return "/index.jsp"; +} +``` + +服务器端,由于映射器适配器需要文件上传解析器,而该解析器默认未被注册,所以手动注册: + +```xml + + + + + + + +``` + +而CommonsMultipartResolver底层使用的Apache的是Common-fileuplad等工具API进行的文件上传 + +```xml + + commons-fileupload + commons-fileupload + 1.4 + +``` + +#### 接收Cookie数据 + +可以使用 `@CookieValue` 注释将 HTTP Cookie 的值绑定到控制器中的方法参数。 + +考虑使用以下 cookie 的请求: + +```Java +JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84 +``` + +下面的示例演示如何获取 cookie 值: + +```Java +@GetMapping("/demo") +public void handle(@CookieValue("JSESSIONID") String cookie) { + //... +} +``` + + + +#### 接收请求头数据 + +可以使用 `@RequestHeader` 批注将请求标头绑定到控制器中的方法参数。 + +请考虑以下带有标头的请求: + +```Java +Host localhost:8080 +Accept text/html,application/xhtml+xml,application/xml;q=0.9 +Accept-Language fr,en-gb;q=0.7,en;q=0.3 +Accept-Encoding gzip,deflate +Accept-Charset ISO-8859-1,utf-8;q=0.7,*;q=0.7 +Keep-Alive 300 +``` + +下面的示例获取 `Accept-Encoding` 和 `Keep-Alive` 标头的值: + +```Java +@GetMapping("/demo") +public void handle( + @RequestHeader("Accept-Encoding") String encoding, + @RequestHeader("Keep-Alive") long keepAlive) { + //... +} +``` + + + +### RESTFul + +REST:**Re**presentational **S**tate **T**ransfer,表现形式状态转换。 + +RESTful 是一种基于 HTTP 和标准化的设计原则的软件架构风格,用于设计和实现可靠、可扩展和易于集成的 Web 服务和应用程序! + +Restful风格的请求,常见的规则有如下三点: + +* 用URI表示某个模块资源,资源名称为名词; +* 用请求方式表示模块具体业务动作,例如:GET表示查询、POST表示插入、PUT表示更新、DELETE表示删除 +* 用HTTP响应状态码表示结果,国内常用的响应包括三部分:状态码、状态信息、响应数据 + +![](./SpringMVC/restful风格.jpeg) + + + +### Javaweb常用对象获取 + +在 JavaWeb 中,共享域指的是在 Servlet 中存储数据,以便在同一 Web 应用程序的多个组件中进行共享和访问。常见的共享域有四种:`ServletContext`、`HttpSession`、`HttpServletRequest`、`PageContext`。 + +1. `ServletContext` 共享域:`ServletContext` 对象可以在整个 Web 应用程序中共享数据,是最大的共享域。一般可以用于保存整个 Web 应用程序的全局配置信息,以及所有用户都共享的数据。在 `ServletContext` 中保存的数据是线程安全的。 +2. `HttpSession` 共享域:`HttpSession` 对象可以在同一用户发出的多个请求之间共享数据,但只能在同一个会话中使用。比如,可以将用户登录状态保存在 `HttpSession` 中,让用户在多个页面间保持登录状态。 +3. `HttpServletRequest` 共享域:`HttpServletRequest` 对象可以在同一个请求的多个处理器方法之间共享数据。比如,可以将请求的参数和属性存储在 `HttpServletRequest` 中,让处理器方法之间可以访问这些数据。 +4. `PageContext` 共享域:`PageContext` 对象是在 JSP 页面Servlet 创建时自动创建的。它可以在 JSP 的各个作用域中共享数据,包括`pageScope`、`requestScope`、`sessionScope`、`applicationScope` 等作用域。 + +共享域的作用是提供了方便实用的方式在同一 Web 应用程序的多个组件之间传递数据,并且可以将数据保存在不同的共享域中,根据需要进行选择和使用。 + +获得Javaweb常见原生对象,有时在我们的Controller方法中需要用到Javaweb的原生对象,例如:Request、 Response等,我们只需要将需要的对象以形参的形式写在方法上,SpringMVC框架在调用Controller方法时,会自动传递实参: + +```java +/** + * 如果想要获取请求或者响应对象,或者会话等,可以直接在形参列表传入,并且不分先后顺序! + * 注意: 接收原生对象,并不影响参数接收! + */ +@GetMapping("api") +@ResponseBody +public String api(HttpSession session , HttpServletRequest request, + HttpServletResponse response){ + String method = request.getMethod(); + System.out.println("method = " + method); + return "api"; +} +``` + + + +### 请求静态资源 + +静态资源请求失效的原因,当DispatcherServlet的映射路径配置为 / 的时候,那么就覆盖的Tomcat容器默认的缺省 Servlet,在Tomcat的config目录下有一个web.xml 是对所有的web项目的全局配置,其中有如下配置: + +```xml + + default + org.apache.catalina.servlets.DefaultServlet + 1 + + + default + / + +``` + +url-pattern配置为 / 的Servlet我们称其为缺省的Servlet,作用是当其他Servlet都匹配不成功时,就找缺省的Servlet ,静态资源由于没有匹配成功的Servlet,所以会找缺省的DefaultServlet,该DefaultServlet具备二次去匹配静态资源的功能。但是我们配置DispatcherServlet后就将其覆盖掉了,而DispatcherServlet会将请求的静态资源的名称当成Controller的映射路径去匹配,即静态资源访问不成功了。 + +静态资源请求的三种解决方案: + +第一种方案,可以再次激活Tomcat的DefaultServlet,Servlet的url-pattern的匹配优先级是:精确匹配>目录匹配> 扩展名匹配>缺省匹配,所以可以指定某个目录下或某个扩展名的资源使用DefaultServlet进行解析: + +```xml + + default + /img/* + + + default + *.html + +``` + +第二种方式,在spring-mvc.xml中去配置静态资源映射,匹配映射路径的请求到指定的位置去匹配资源 + +```xml + + + + + +``` + +第三种方式,在spring-mvc.xml中去配置 `< mvc:default-servlet-handler >`,该方式是注册了一个 DefaultServletHttpRequestHandler 处理器,静态资源的访问都由该处理器去处理,这也是开发中使用最多的。 + + + +### 注解驱动``标签 + +静态资源配置的第二第三种方式我们可以正常访问静态资源了,但是Controller又无法访问了,报错404,即找不到对应的资源。 + +第二种方式是通过SpringMVC去解析mvc命名空间下的resources标签完成的静态资源解析,第三种方式式通过 SpringMVC去解析mvc命名空间下的default-servlet-handler标签完成的静态资源解析,根据前面所学习的自定义命名空间的解析的知识,可以发现不管是以上哪种方式,最终都会注册SimpleUrlHandlerMapping + +```java +public BeanDefinition parse(Element element, ParserContext context) { + //创建SimpleUrlHandlerMapping类型的BeanDefinition + RootBeanDefinition handlerMappingDef = + new RootBeanDefinition(SimpleUrlHandlerMapping.class); + //注册SimpleUrlHandlerMapping的BeanDefinition + context.getRegistry().registerBeanDefinition(beanName, handlerMappingDef); +} +``` + +一旦SpringMVC容器中存在 HandlerMapping 类型的组件时,前端控制器 DispatcherServlet在进行初始化时,就会从容器中获得HandlerMapping ,不再加载 dispatcherServlet.properties 中默认处理器映射器策略,那也就意味着RequestMappingHandlerMapping不会被加载到了。 + +手动将RequestMappingHandlerMapping也注册到SpringMVC容器中就可以了,这样DispatcherServlet在进行初 始化时,就会从容器中同时获得RequestMappingHandlerMapping存储到DispatcherServlet中名为 handlerMappings的List集合中,对@RequestMapping 注解进行解析。 + +```xml + +``` + +所以,要想使用@RequestMapping正常映射到资源方法,同时静态资源还能正常访问, 还可以将请求json格式字符串和JavaBean之间自由转换,我们就需要在spring-mvc.xml中尽心如下配置: + +```xml + + + + + + + + + + + + +``` + +Spring将上述配置浓缩成了一个简单的配置标签,那就是mvc的注解驱动,该标签内部会帮我们注册RequestMappingHandlerMapping、注册 RequestMappingHandlerAdapter并注入Json消息转换器等,上述配置就可以简化成如下: + +```xml + + + + +``` + + + +## SpringMVC的响应处理 + +### 转发和重定向 + +在 Spring MVC 中,Handler 方法返回值来实现快速转发,可以使用 `redirect` 或者 `forward` 关键字来实现重定向。 + +![](./SpringMVC/转发和重定向.jpeg) + +### 模型数据 + +响应模型数据,响应模型数据本质也是转发,在转发时可以准备模型数据 + +```java +@GetMapping("/forward5") +public ModelAndView forward5(ModelAndView modelAndView){ + //准备JavaBean模型数据 + User user = new User(); + user.setUsername("haohao"); + //设置模型 + modelAndView.addObject("user",user); + //设置视图 + modelAndView.setViewName("/index.jsp"); + return modelAndView; +} +``` + +### 直接写回数据 + +直接回写数据,直接通过方法的返回值返回给客户端的字符串,但是SpringMVC默认的方法返回值是视图,可以通过 @ResponseBody 注解显示的告知此处的返回值不要进行视图处理,是要以响应体的方式处理的 + +```java +@GetMapping("/response2") +@ResponseBody +public String response2() throws IOException { + return "Hello haohao!"; +} +``` + +### 返回json数据 + +**@ResponseBody** + +接收请求数据时,客户端提交的Json格式的字符串,也是使用Jackson进行的手动转换成JavaBean ,可以当我们使用了@RequestBody时,直接用JavaBean就接收了Json格式的数据,原理其实就是SpringMVC底层 帮我们做了转换,此处@ResponseBody也可以将JavaBean自动给我们转换成Json格式字符串回响应 + +`@ResponseBody` 注解可以用来标识方法或者方法返回值,表示方法的返回值是要直接返回给客户端的数据,而不是由视图解析器来解析并渲染生成响应体(viewResolver没用)。 + +```Java +@RequestMapping(value = "/user/detail", method = RequestMethod.POST) +@ResponseBody +public User getUser(@RequestBody User userParam) { + System.out.println("userParam = " + userParam); + User user = new User(); + user.setAge(18); + user.setName("John"); + //返回的对象,会使用jackson的序列化工具,转成json返回给前端! + return user; +} +``` + +**@RestController** + +类上的 @ResponseBody 注解可以和 @Controller 注解合并为 @RestController 注解。所以使用了 @RestController 注解就相当于给类中的每个方法都加了 @ResponseBody 注解。 + +RestController源码: + +```Java +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Controller +@ResponseBody +public @interface RestController { + + /** + * The value may indicate a suggestion for a logical component name, + * to be turned into a Spring bean in case of an autodetected component. + * @return the suggested component name, if any (or empty String otherwise) + * @since 4.0.1 + */ + @AliasFor(annotation = Controller.class) + String value() default ""; + +} +``` + + + +## SpringMVC的拦截器 + +SpringMVC的拦截器Interceptor规范,主要是对Controller资源访问时进行拦截操作的技术,当然拦截后可以进行权限控制,功能增强等都是可以的。拦截器有点类似 Javaweb 开发中的Filter,拦截器与Filter的区别如下图: + +![](./SpringMVC/拦截器.jpeg) + +| | Filter 技术 | Interceptor 技术 | +| ------------- | ------------------------------------------------------- | ------------------------------------------------------------ | +| 技术范畴 | Javaweb原生技术 | SpringMVC框架技术 | +| 拦截/过滤资源 | 可以对所有请求都过滤,包括任何Servlet、Jsp、 其他资源等 | 只对进入了SpringMVC管辖范围的才拦截,主要拦截 Controller请求 | +| 执行时机 | 早于任何Servlet执行 | 晚于DispatcherServlet执行 | + +实现了HandlerInterceptor接口,且被Spring管理的Bean都是拦截器,接口定义如下: + +```java +public interface HandlerInterceptor { + default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + return true; + } + + default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { + } + + default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { + } +} +``` + +| | 作用 | 参数 | 返回值 | +| --------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | +| preHandle | 对拦截到的请求进行预处理,返回true放行执行处理器方法,false不放行 | Handler是拦截到的Controller方 法处理器 | 一旦返回false,代表终止向后执行,所有后置方法都不执行, 最终方法只执行对应preHandle 返回了true的 | +| postHandle | 在处理器的方法执行后,对拦截到的请求进行后处理,可以在方法中对模型数据和 视图进行修改 | Handler是拦截到的Controller方 法处理器;modelAndView是返 回的模型视图对象 | | +| afterCompletion | 视图渲染完成后(整个流程结束之后),进行最后的处理,如果请求流程中有异常,可 以处理异常对象 | Handler是拦截到的Controller方 法处理器;ex是异常对象 | | + + + +拦截器执行顺序取决于 interceptor 的配置顺序。 + +当每个拦截器都是放行状态时,三个方法的执行顺序如下: + +![](./SpringMVC/拦截器执行顺序.jpeg) + +当Interceptor1和Interceptor2处于放行,Interceptor3处于不放行时,三个方法的执行顺序如下: + +![](./SpringMVC/拦截器执行顺序2.jpeg) + +1. 编写MyInterceptor01实现HandlerInterceptor接口: + + ```java + public class MyInterceptor01 implements HandlerInterceptor { + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object + handler) throws Exception { + System.out.println("Controller方法执行之前..."); + return true;//放行 + } + @Override + public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, + ModelAndView modelAndView) throws Exception { + System.out.println("Controller方法执行之后..."); + } + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object + handler, Exception ex) throws Exception { + System.out.println("渲染视图结束,整个流程完毕..."); + } + } + ``` + +2. 配置Interceptor: + + ```xml + + + + + + + + + ``` + + + +### 拦截器执行原理 + +![](./SpringMVC/拦截器执行原理.jpeg) + +请求到来时先会使用组件HandlerMapping去匹配Controller的方法(Handler)和符合拦截路径的Interceptor, Handler和多个Interceptor被封装成一个HandlerExecutionChain的对象 HandlerExecutionChain 定义如下: + +```java +public class HandlerExecutionChain { + //映射的Controller的方法 + private final Object handler; + //当前Handler匹配的拦截器集合 + private final List interceptorList; + // ... 省略其他代码 ... +} +``` + +在DispatcherServlet的doDispatch方法中执行拦截器: + +```java +protected void doDispatch(HttpServletRequest request, HttpServletResponse response){ + //根据请求信息获得HandlerExecutionChain + HandlerExecutionChain mappedHandler = this.getHandler(request); + //获得处理器适配器 + HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler()); + //执行Interceptor的前置方法,前置方法如果返回false,则该流程结束 + if (!mappedHandler.applyPreHandle(request, response)) { + return; + } + //执行handler,一般是HandlerMethod + ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); + //执行后置方法 + mappedHandler.applyPostHandle(processedRequest, response, mv); + //执行最终方法 + this.triggerAfterCompletion(processedRequest, response, mappedHandler, e); +} +``` + +HandlerExecutionChain的applyPreHandle方法源码: + +```java +boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws + Exception { + //对interceptorList进行遍历,正向遍历,与此同时使用interceptorIndex进行计数 + for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) { + //取出每一个Interceptor对象 + HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i); + //调用Interceptor的preHandle方法,如果返回false,则直接执行Interceptor的最终方法 + if (!interceptor.preHandle(request, response, this.handler)) { + //执行Interceptor的最终方法 + this.triggerAfterCompletion(request, response, (Exception)null); + return false; + } + } + return true; +} +``` + +HandlerExecutionChain的applyPostHandle方法源码: + +```java +void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable + ModelAndView mv) throws Exception { + //对interceptorList进行遍历,逆向遍历 + for(int i = this.interceptorList.size() - 1; i >= 0; --i) { + //取出每一个Interceptor + HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i); + //执行Interceptor的postHandle方法 + interceptor.postHandle(request, response, this.handler, mv); + } +} +``` + +HandlerExecutionChain的triggerAfterCompletion方法源码: + +```java +void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable + Exception ex) { + //逆向遍历interceptorList,遍历的个数为执行的applyPreHandle次数-1 + for(int i = this.interceptorIndex; i >= 0; --i) { + //取出每一个Interceptor + HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i); + try { + //执行Interceptor的afterCompletion方法 + interceptor.afterCompletion(request, response, this.handler, ex); + } catch (Throwable var7) { + logger.error("HandlerInterceptor.afterCompletion threw exception", var7); + } + } +} +``` + + + +## SpringMVC的全注解开发 + +xml配置文件使用核心配置类替代,xml中的标签使用对应的注解替代 + +```xml + + + + + + + + + + + + + + + +``` + +组件扫描,可以通过@ComponentScan注解完成; + +文件上传解析器multipartResolver可以通过非自定义Bean的注解配置方式,即@Bean注解完成; + +` `和 `` 通过 @EnableWebMvc 完成。 + +```java +@Configuration +@ComponentScan("com.itheima.controller") +@EnableWebMvc +public class SpringMVCConfig { + @Bean + public CommonsMultipartResolver multipartResolver(){ + CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(); + multipartResolver.setDefaultEncoding("UTF-8"); + multipartResolver.setMaxUploadSize(3145728); + multipartResolver.setMaxUploadSizePerFile(1048576); + multipartResolver.setMaxInMemorySize(1048576); + return multipartResolver; + } +} +``` + +配置Spring和SpringMVC的入口: + +```java +public class MyAnnotationConfigDispatcherServletInitializer extends + AbstractAnnotationConfigDispatcherServletInitializer { + //提供Spring容器的核心配置类 + protected Class[] getRootConfigClasses() { + System.out.println("加载核心配置类创建ContextLoaderListener"); + return new Class[]{ApplicationContextConfig.class}; + } + //提供SpringMVC容器的核心配置类 + protected Class[] getServletConfigClasses() { + System.out.println("加载核心配置类创建DispatcherServlet"); + return new Class[]{SpringMVCConfig.class}; + } + //提供前端控制器的映射路径 + protected String[] getServletMappings() { + return new String[]{"/"}; + } +} +``` + + + +```JAVA +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +@Documented +@Import({DelegatingWebMvcConfiguration.class}) +public @interface EnableWebMvc {} +``` + +@EnableWebMvc内部通过@Import 导入了DelegatingWebMvcConfiguration类 + +```Java +@Configuration(proxyBeanMethods = false) +public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport { + private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite(); + //从容器中注入WebMvcConfigurer类型的Bean + @Autowired(required = false) + public void setConfigurers(List configurers) { + if (!CollectionUtils.isEmpty(configurers)) { + this.configurers.addWebMvcConfigurers(configurers); + } + } + //省略其他代码 +} +``` + +WebMvcConfigurer类型的Bean会被注入进来,然后被自动调用,所以可以实现WebMvcConfigurer接口,完成一些解析器、默认Servlet等的指定,WebMvcConfigurer接口定义如下: + +```Java +public interface WebMvcConfigurer { + //配置默认Servet处理器 + default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { } + //添加拦截器 + default void addInterceptors(InterceptorRegistry registry) { } + //添加资源处理器 + default void addResourceHandlers(ResourceHandlerRegistry registry) { } + //添加视图控制器 + default void addViewControllers(ViewControllerRegistry registry) { } + //配置视图解析器 + default void configureViewResolvers(ViewResolverRegistry registry) { } + //添加参数解析器 + default void addArgumentResolvers(List resolvers) { } + //... 省略其他代码 ... +} +``` + +创建MyWebMvcConfigurer实现WebMvcConfigurer接口,实现addInterceptors 和 configureDefaultServletHandling方法 + +```java +@Component +public class MyWebMvcConfigurer implements WebMvcConfigurer { + @Override + public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { + //开启DefaultServlet,可以处理静态资源了 + configurer.enable(); + } + @Override + public void addInterceptors(InterceptorRegistry registry) { + //创建拦截器对象,进行注册 + //Interceptor的执行顺序也取决于添加顺序 + registry.addInterceptor(new MyInterceptor01()).addPathPatterns("/*"); + } +} +``` + + + +## SpringMVC的组件原理 + +### 前端控制器初始化 + +前端控制器DispatcherServlet是SpringMVC的入口,也是SpringMVC的大脑,主流程的工作都是在此完成的。DispatcherServlet 本质是个Servlet,当配置了 load-on-startup 时,会在服务器启动时就执行创建和执行初始化init方法,每次请求都会执行service方法 DispatcherServlet 的初始化主要做了两件事: + +* 获得了一个 SpringMVC 的 ApplicationContext容器; +* 注册了 SpringMVC的九大组件 + +![](./SpringMVC/前端控制器.jpeg) + +SpringMVC 的ApplicationContext容器创建时机,Servlet 规范的 init(ServletConfig config) 方法经过子类重写 ,最终会调用 FrameworkServlet 抽象类的initWebApplicationContext() 方法,该方法中最终获得 一个根 Spring容器(Spring产生的),一个子Spring容器(SpringMVC产生的). + +HttpServletBean 的初始化方法: + +```java +public final void init() throws ServletException { + this.initServletBean(); +} +``` + +FrameworkServlet的initServletBean方法: + +```java +protected final void initServletBean() throws ServletException { + this.webApplicationContext = this.initWebApplicationContext();//初始化ApplicationContext + this.initFrameworkServlet();//模板设计模式,供子类覆盖实现,但是子类DispatcherServlet没做使用 +} +``` + +在initWebApplicationContext方法中体现的父子容器的逻辑关系: + +```java +//初始化ApplicationContext是一个及其关键的代码 +protected WebApplicationContext initWebApplicationContext() { + //获得根容器,其实就是通过ContextLoaderListener创建的ApplicationContext + //如果配置了ContextLoaderListener则获得根容器,没配置获得的是null + WebApplicationContext rootContext = + WebApplicationContextUtils.getWebApplicationContext(this.getServletContext()); + //定义SpringMVC产生的ApplicationContext子容器 + WebApplicationContext wac = null; + if (wac == null) { + //==>创建SpringMVC的子容器,创建同时将Spring的创建的rootContext传递了过去 + wac = this.createWebApplicationContext(rootContext); + } + //将SpringMVC产生的ApplicationContext子容器存储到ServletContext域中 + //key名是:org.springframework.web.servlet.FrameworkServlet.CONTEXT.DispatcherServlet + if (this.publishContext) { + String attrName = this.getServletContextAttributeName(); + this.getServletContext().setAttribute(attrName, wac); + } +} +``` + +```java +protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext + parent) { + //实例化子容器ApplicationContext + ConfigurableWebApplicationContext wac = + (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass); + //设置传递过来的ContextLoaderListener的rootContext为父容器 + wac.setParent(parent); + //获得web.xml配置的classpath:spring-mvc.xml + String configLocation = this.getContextConfigLocation(); + if (configLocation != null) { + //为子容器设置配置加载路径 + wac.setConfigLocation(configLocation); + } + //初始化子容器(就是加载spring-mvc.xml配置的Bean) + this.configureAndRefreshWebApplicationContext(wac); + return wac; +} +``` + +父容器和子容器概念和关系: + +* 父容器:Spring 通过ContextLoaderListener为入口产生的applicationContext容器,内部主要维护的是 applicationContext.xml(或相应配置类)配置的Bean信息; +* 子容器:SpringMVC通过DispatcherServlet的init() 方法产生的applicationContext容器,内部主要维护的是spring-mvc.xml(或相应配置类)配置的Bean信息,且内部还通过parent属性维护这父容器的引用。 +* Bean的检索顺序:根据上面子父容器的概念,可以知道Controller存在与子容器中,而Controller中要注入 Service时,会先从子容器本身去匹配,匹配不成功时在去父容器中去匹配,于是最终从父容器中匹配到的 UserService,这样子父容器就可以进行联通了。但是父容器只能从自己容器中进行匹配,不能从子容器中进行匹配。 + + + +注册 SpringMVC的九大组件,在初始化容器initWebApplicationContext方法中无论是否含有MVC容器总会执行configureAndRefreshWebApplicationContext方法,进而执行refresh方法中的过程,然后finishRefresh方法中发布事件 + +```java +protected void finishRefresh() { + + // Publish the final event. + publishEvent(new ContextRefreshedEvent(this)); +} +``` + +FrameworkServlet中的内部类ContextRefreshListener监听事件,进而执行了onRefresh方法,最终执行了初始化策略initStrategies方法,注册了九个解析器组件 + +```java +public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware { + private class ContextRefreshListener implements ApplicationListener { + + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + FrameworkServlet.this.onApplicationEvent(event); + } + } + public void onApplicationEvent(ContextRefreshedEvent event) { + this.refreshEventReceived = true; + synchronized (this.onRefreshMonitor) { + onRefresh(event.getApplicationContext()); + } + } +} +``` + +```java +//DispatcherServlet初始化SpringMVC九大组件 +protected void initStrategies(ApplicationContext context) { + this.initMultipartResolver(context);//1、初始化文件上传解析器 + this.initLocaleResolver(context);//2、初始化国际化解析器 + this.initThemeResolver(context);//3、初始化模板解析器 + this.initHandlerMappings(context);//4、初始化处理器映射器 + this.initHandlerAdapters(context);//5、初始化处理器适配器 + this.initHandlerExceptionResolvers(context);//6、初始化处理器异常解析器 + this.initRequestToViewNameTranslator(context);//7、初始化请求视图转换器 + this.initViewResolvers(context);//8、初始化视图解析器 + this.initFlashMapManager(context);//9、初始化lashMapManager策略组件 +} +``` + + + +### 前端控制器执行主流程 + +![](./SpringMVC/组件关系.jpeg) + +**核心组件:** + +1. **DispatcherServlet** : **前端控制器**,负责接收请求、分发,并给予客户端响应。 +2. **HandlerMapping** : **处理器映射器**,根据 URL 去匹配查找能处理的 `Handler` ,并会将请求涉及到的拦截器和 `Handler` 一起封装。 +3. **HandlerAdapter** : **处理器适配器**,根据 `HandlerMapping` 找到的 `Handler` ,适配执行对应的 `Handler`; +4. **Handler** : **请求处理器**,处理实际请求的处理器。 +5. **ViewResovler** : **视图解析器**,根据 `Handler` 返回的逻辑视图 / 视图,解析并渲染真正的视图,并传递给 `DispatcherServlet` 响应客户端 + + + +**执行流程:** + +1. 客户端(浏览器)发送请求, `DispatcherServlet`拦截请求。 + +2. `DispatcherServlet` 根据请求信息调用 `HandlerMapping` 。`HandlerMapping` 根据 URL 去匹配查找能处理的 `Handler`( `Controller` 控制器) ,并会将请求涉及到的拦截器和 `Handler` 一起封装。 + +3. `DispatcherServlet` 调用 `HandlerAdapter`适配器执行 `Handler` 。 + +4. `Handler` 完成对用户请求的处理后,会返回一个 `ModelAndView` 对象给`DispatcherServlet`,`ModelAndView` 顾名思义,包含了数据模型以及相应的视图的信息。`Model` 是返回的数据对象,`View` 是个逻辑上的 `View`。 + +5. `ViewResolver` 会根据逻辑 `View` 查找实际的 `View`。 + +6. `DispaterServlet` 把返回的 `Model` 传给 `View`(视图渲染)。 + +7. 把 `View` 返回给请求者(浏览器) + + + +FrameworkServlet 复写了service(HttpServletRequest request, HttpServletResponse response) 、doGet(HttpServletRequest request, HttpServletResponse response)、doPost(HttpServletRequest request, HttpServletResponse response)等方法,这些方法都会调用processRequest方法 + +```java +protected final void processRequest(HttpServletRequest request, HttpServletResponse response){ + this.doService(request, response); +} +``` + +进一步调用了doService方法,该方法内部又调用了doDispatch方法,而SpringMVC 主流程最核心的方法就是 doDispatch 方法 + +```java +protected void doService(HttpServletRequest request, HttpServletResponse response) { + this.doDispatch(request, response); +} +``` + +doDispatch方法源码: + +```java +protected void doDispatch(HttpServletRequest request, HttpServletResponse response) { + HttpServletRequest processedRequest = request; + HandlerExecutionChain mappedHandler = null; //定义处理器执行链对象 + ModelAndView mv = null; //定义模型视图对象 + //匹配处理器映射器HandlerMapping,返回处理器执行链对象(包含拦截器和当前要被执行的方法的handler对象) + mappedHandler = this.getHandler(processedRequest); + //匹配处理器适配器HandlerAdapter,返回处理器适配器对象 + HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler()); + //执行Interceptor的前置方法preHandle + mappedHandler.applyPreHandle(processedRequest, response); + //处理器适配器执行控制器Handler,返回模型视图对象 + mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); + //执行Interceptor的后置方法postHandle + mappedHandler.applyPostHandle(processedRequest, response, mv); + //获取视图渲染视图 + this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException); +} +``` + + + +## SpringMVC的异常处理 + +### SpringMVC 异常的处理流程 + +SpringMVC 处理异常的思路是,一路向上抛,都抛给前端控制器 DispatcherServlet ,DispatcherServlet 在调用异常处理器ExceptionResolver进行处理 + +![](./SpringMVC/异常处理流程.jpeg) + +### SpringMVC 的异常处理方式 + +SpringMVC 提供了以下三种处理异常的方式: + +* 简单异常处理器:使用SpringMVC 内置的异常处理器处理 SimpleMappingExceptionResolver; +* 自定义异常处理器:实现HandlerExceptionResolver接口,自定义异常进行处理; +* 注解方式:使用@ControllerAdvice + @ExceptionHandler 来处理 + +使用注解 @ControllerAdvice + @ExceptionHandler 配置异常,@ControllerAdvice 注解本质是一个 @Component,也会被扫描到,与此同时,具备AOP功能,默认情况下对所有的Controller都进行拦截操作, 拦截后干什么呢?就需要在结合@ExceptionHandler、@InitBinder、@ModelAttribute 注解一起使用了,此处我们讲解的是异常,所以是@ControllerAdvice + @ExceptionHandler的组合形式。 + +```java +@ControllerAdvice +public class GlobalExceptionHandler { + @ExceptionHandler(RuntimeException.class) + public ModelAndView runtimeHandleException(RuntimeException e){ + System.out.println("全局异常处理器执行...."+e); + ModelAndView modelAndView = new ModelAndView("/error.html"); + return modelAndView; + } + @ExceptionHandler(IOException.class) + @ResponseBody + public ResultInfo ioHandleException(IOException e){ + //模拟一个ResultInfo + ResultInfo resultInfo = new ResultInfo(0,"IOException",null); + return resultInfo; + } +} +``` + +如果全局异常处理器响应的数据都是Json格式的字符串的话,可以使用@RestControllerAdvice替代 @ControllerAdvice 和 @ResponseBody + +### SpringMVC 常用的异常解析器 + +![](./SpringMVC/异常解析器.jpeg) diff --git a/docs/SpringMVC/SpringMVC/SpringMVC.jpeg b/docs/SpringMVC/SpringMVC/SpringMVC.jpeg new file mode 100644 index 0000000..2be06c7 Binary files /dev/null and b/docs/SpringMVC/SpringMVC/SpringMVC.jpeg differ diff --git "a/docs/SpringMVC/SpringMVC/restful\351\243\216\346\240\274.jpeg" "b/docs/SpringMVC/SpringMVC/restful\351\243\216\346\240\274.jpeg" new file mode 100644 index 0000000..1c9d613 Binary files /dev/null and "b/docs/SpringMVC/SpringMVC/restful\351\243\216\346\240\274.jpeg" differ diff --git "a/docs/SpringMVC/SpringMVC/\345\211\215\347\253\257\346\216\247\345\210\266\345\231\250.jpeg" "b/docs/SpringMVC/SpringMVC/\345\211\215\347\253\257\346\216\247\345\210\266\345\231\250.jpeg" new file mode 100644 index 0000000..6013d4a Binary files /dev/null and "b/docs/SpringMVC/SpringMVC/\345\211\215\347\253\257\346\216\247\345\210\266\345\231\250.jpeg" differ diff --git "a/docs/SpringMVC/SpringMVC/\345\274\202\345\270\270\345\244\204\347\220\206\346\265\201\347\250\213.jpeg" "b/docs/SpringMVC/SpringMVC/\345\274\202\345\270\270\345\244\204\347\220\206\346\265\201\347\250\213.jpeg" new file mode 100644 index 0000000..7021c29 Binary files /dev/null and "b/docs/SpringMVC/SpringMVC/\345\274\202\345\270\270\345\244\204\347\220\206\346\265\201\347\250\213.jpeg" differ diff --git "a/docs/SpringMVC/SpringMVC/\345\274\202\345\270\270\350\247\243\346\236\220\345\231\250.jpeg" "b/docs/SpringMVC/SpringMVC/\345\274\202\345\270\270\350\247\243\346\236\220\345\231\250.jpeg" new file mode 100644 index 0000000..f230756 Binary files /dev/null and "b/docs/SpringMVC/SpringMVC/\345\274\202\345\270\270\350\247\243\346\236\220\345\231\250.jpeg" differ diff --git "a/docs/SpringMVC/SpringMVC/\346\213\246\346\210\252\345\231\250.jpeg" "b/docs/SpringMVC/SpringMVC/\346\213\246\346\210\252\345\231\250.jpeg" new file mode 100644 index 0000000..d246c07 Binary files /dev/null and "b/docs/SpringMVC/SpringMVC/\346\213\246\346\210\252\345\231\250.jpeg" differ diff --git "a/docs/SpringMVC/SpringMVC/\346\213\246\346\210\252\345\231\250\346\211\247\350\241\214\345\216\237\347\220\206.jpeg" "b/docs/SpringMVC/SpringMVC/\346\213\246\346\210\252\345\231\250\346\211\247\350\241\214\345\216\237\347\220\206.jpeg" new file mode 100644 index 0000000..9a6743e Binary files /dev/null and "b/docs/SpringMVC/SpringMVC/\346\213\246\346\210\252\345\231\250\346\211\247\350\241\214\345\216\237\347\220\206.jpeg" differ diff --git "a/docs/SpringMVC/SpringMVC/\346\213\246\346\210\252\345\231\250\346\211\247\350\241\214\351\241\272\345\272\217.jpeg" "b/docs/SpringMVC/SpringMVC/\346\213\246\346\210\252\345\231\250\346\211\247\350\241\214\351\241\272\345\272\217.jpeg" new file mode 100644 index 0000000..b3c0035 Binary files /dev/null and "b/docs/SpringMVC/SpringMVC/\346\213\246\346\210\252\345\231\250\346\211\247\350\241\214\351\241\272\345\272\217.jpeg" differ diff --git "a/docs/SpringMVC/SpringMVC/\346\213\246\346\210\252\345\231\250\346\211\247\350\241\214\351\241\272\345\272\2172.jpeg" "b/docs/SpringMVC/SpringMVC/\346\213\246\346\210\252\345\231\250\346\211\247\350\241\214\351\241\272\345\272\2172.jpeg" new file mode 100644 index 0000000..1ed9a31 Binary files /dev/null and "b/docs/SpringMVC/SpringMVC/\346\213\246\346\210\252\345\231\250\346\211\247\350\241\214\351\241\272\345\272\2172.jpeg" differ diff --git "a/docs/SpringMVC/SpringMVC/\347\273\204\344\273\266\345\205\263\347\263\273.jpeg" "b/docs/SpringMVC/SpringMVC/\347\273\204\344\273\266\345\205\263\347\263\273.jpeg" new file mode 100644 index 0000000..c730734 Binary files /dev/null and "b/docs/SpringMVC/SpringMVC/\347\273\204\344\273\266\345\205\263\347\263\273.jpeg" differ diff --git "a/docs/SpringMVC/SpringMVC/\350\275\254\345\217\221\345\222\214\351\207\215\345\256\232\345\220\221.jpeg" "b/docs/SpringMVC/SpringMVC/\350\275\254\345\217\221\345\222\214\351\207\215\345\256\232\345\220\221.jpeg" new file mode 100644 index 0000000..3162a8b Binary files /dev/null and "b/docs/SpringMVC/SpringMVC/\350\275\254\345\217\221\345\222\214\351\207\215\345\256\232\345\220\221.jpeg" differ