第六章 请求源码
DispatcherServlet结构分析 == Servlet
接收所有请求
在FrameworkServlet中复写 doGet 和doPost -> abstract doService
DispatcherServlet 源码运行分析 -> controller -> 页面
什么时候运行了目标方法 利用反射运行
什么时候运行了页面
doDispatch方法详解
第一步 判断区别是不是多文件上传请求
第二步 找到处理器 -> 处理器映射RequestMapping
第三步 拿到适配器来处理 适配器封装了一个专门请求注解方法执行的工具
第四步 控制台输出了 :运行了目标方法 ; 通过上面获取的适配器ha来执行 不管是否为String 还是Model 最终返回了一个modelAndView
下面是看看是不是异步处理
没有视图名 默认给一个视图名 请求的地址是什么 作为默认
第五步 程序运行了页面 根据方法最终执行完成封装的ModelAndView 转发到对应页面 而且ModelAndView中的数据可以从请求域中获取
getHandler详细处理过程:怎么根据当前请求就能找到哪个类能来处理
- 返回的是目标Handler执行链 而不是method.invoke();
- : 在目标方法执行的时候 用*拦截一下
processedRequest:请求地址
- 怎么通过请求地址找到目标类 或者目标方法
handlerMappings:处理器映射:里面保存了每一个处理器能处理哪些请求(都标志了注解了)的映射信息 ,有两个:基于注解/写配置Bean
全部都保持在handlerMap 【注解下面的handlerMap不为空,而配置下的handlerMap下面为{}空】 "/hello"【helloController】 "/requ"【MyController】 所有的控制器都会被扫描 并保存到里面
handlerMap:ioc容器自动创建Controller对象的时候扫描每一个处理器都能处理什么请求,保存在handlerMapping的hadllerMap属性中;下一次请求过来,就来看那那个HandlerMapping中由这个请求映射信息就行了
如何找目标处理类的适配器,要拿适配器才能去执行目标方法
- 通过for循环找到合适的适配器
一共有三个适配器
第一个适配需要实现HttpRequestHandler接口 (我们处理器不是这种类型 没有继承任何接口)
第二个需要继承Controller接口 之前的方法 老版本的 我们也不是
第三个注解版的适配器 处理器中只要有标了注解的方法就能用
拿到适配器了 执行目标方法
SpringMVC 九大组件 关键位置都是由这些组件完成的
九大组件初始化的地方
也就是ioc一启动 上面的方法就会被运行 初始化策略方法()
初始化
如果需要DispatcherServlet修改某些属性变量的默认配置 可以用web.xml配置
一些配置信息都写在Properties
MultipartResolver(多部件解析器 文件处理器),对应的初始化方法是initMultipartResolver(context),用于处理上传请求。处理方法是将普通的request包装成MultipartHttpServletRequest,后者可以直接调用getFile方法获取File。
LocaleResolver(区域信息解析器+跟国际化 当前环境处理器),对应的初始化方法是initLocaleResolver(context),这就相当于配置数据库的方言一样,有了这个就可以对不同区域的用户显示不同的结果。SpringMVC主要有两个地方用到了Locale:一是ViewResolver视图解析的时候;二是用到国际化资源或者主题的时候。
ThemeResolver(主题处理器:主题效果更换),对应的初始化方法是initThemeResolver(context),用于解析主题。SpringMVC中一个主题对应一个properties文件,里面存放着跟当前主题相关的所有资源,如图片、css样式等。SpringMVC的主题也支持国际化。
HandlerMappings(处理器映射器),对应的初始化方法是initHandlerMappings(context),这就是根据用户请求的资源uri来查找Handler的。在SpringMVC中会有很多请求,每个请求都需要一个Handler处理,具体接收到一个请求之后使用哪个Handler进行处理呢
HandlerAdapters(处理器适配器),对应的初始化方法是initHandlerAdapters(context),从名字上看,它就是一个适配器。Servlet需要的处理方法的结构却是固定的,都是以request和response为参数的方法。如何让固定的Servlet处理方法调用灵活的Handler来进行处理呢?这就是HandlerAdapters要做的事情。
HandlerExceptionResolvers(异常处理器:springmvc强大的异常解析功能),对应的初始化方法是initHandlerExceptionResolvers(context),其它组件都是用来干活的。在干活的过程中难免会出现问题,出问题后怎么办呢?这就要有一个专门的角色对异常情况进行处理,在SpringMVC中就是HandlerExceptionResolver。
RequestToViewNameTranslator(视图名称翻译器),对应的初始化方法是initRequestToViewNameTranslator(context),有的Handler处理完后并没有设置View也没有设置ViewName,这时就需要从request获取ViewName了,如何从request中获取ViewName就是RequestToViewNameTranslator要做的事情了。
ViewResolvers(页面渲染处理器 ),对应的初始化方法是initViewResolvers(context),ViewResolver用来将String类型的视图名和Locale解析为View类型的视图。View是用来渲染页面的,也就是将程序返回的参数填入模板里,生成html(也可能是其它类型)文件。
FlashMapManager(参数传递管理器:springmvc中运行重定向携带数据定 功能),对应的初始化方法是initFlashMapManager(context),用来管理FlashMap的,FlashMap主要用在redirect重定向中传递参数。
九大组件都是接口:规范程序 提供了非常强大的扩展性
组件的初始化:
- 有些组件在容器中是使用类型找的,有些组件是使用id找的
- 去容器中找到这个组件,如果没有找到就用默认的配置
锁定目标方法的执行
@Controller public class MyController { @RequestMapping("requ") //多参数来解决 public String getBook(@RequestParam("bookValue")Double bookValue,@ModelAttribute("book") Book book,Model model) { return "hello"; } @ModelAttribute public void prePro(Map map) { Book book = new Book(); book.setBookName("西游记"); System.out.println("ModelAttribute....."); } }
- 第一步 看看目标类有没有SessionAttribute 注解
- 第二步 进入
- 第三步 获取到方法解析器 通过方法解析器得到用哪个方法 【根据当前请求地址找到真正的目标方法】 还得到方法执行器 重新包赚request和response 还new了一个BindingAwareModelMap();【隐含模型】 所有的数据都在请求域中
- 第四步 这个方***运行ModelAttribute方法 和 处理器方法
目标方法利用反射执行期间确定参数值,提前执行modelattribute等所有的操作都在方法中
modelAttribute
围绕这个方法进行实验
第一步 方法执行的细节
所有参数
第二步看看参数有没有Attribute 然后放在隐含模型中
第三步 看看ModelAttributeMethod方法有哪些 找到所有ModelAttribute注解标注的方法
attributeMehodToInvoke 就是要执行的方法 例如hahaModelAttribute
确定值
下面的代码是resolveHandlerArguments()
确定方法运行时使用的每一个参数的值:上面args里面的方法 其中paramtypes参数类型是一个Map类 而args是一个和参数个数一样多的数组,会保存每个参数的值 下面是处理args赋值的方法
其中上面的Annotation[] ParamAnns = methodParam.getParamterAnnotations();如果参数中没有任何的注解 则为空,如果有注解 对各种注解进行分类 进行判断时利用request等方法获取对应的值
前一张图对方法里面参数进行遍历 每一个参数编写了注解只能用一个 超过1就报错
解析普通参数
进入resolveCommonArgument 因为没有自定义 所以这里为空 到下一个图
解析标准参数:
解析标准参数:判断是不是ServletRequest、ServletResponse...原生API 因为我们传入的是Map,什么都是不是就随便new一个Object
如果没有注解(resolveCommonArgument--resolveStandardArgument),先看是否是普通参数,就是确定当前的参数是否是原生API 【request】等等在注解为Annotation=0的情况下,看看参数是否是Map类型 如果是将之前的隐含模型传给Map/Model旗下
下一步是看看modelAttribute是否有value,如@ModelAttribute(value="haha") 我们没有为"",attrName=value 也是为""且为这个变量new一个变量名,返回值类型首字母小写void/book
因为方法是void 所以attrValue是null
向隐含模型里面放值
总体上 @modelAttribute的作用就是运行 放数据
执行目标方法前的准备
对ModelAttribute目标方法参数进行赋值【标了注解 没标注解】
目标方法的参数
第一个参数 从request里获取【多值或者单值】
第二个参数 Map 没有注解 隐含模型赋值过去 至始至终都是用隐含模型赋值
第三个参数 request对象 解析普通参数里面 确定API
第四个参数自定义 book注入【有注解】 attrName如果是空串 赋值为参数类型首字母小写 作为值 如book 不看名字book123 然后去隐含模型里面找
总结
book注入【有无注解的步骤】
重要的两件事
modelAttribute知识点补充
@SessionAttributes(value="msg") @Controller public class MyController { @RequestMapping("requ") public String getBook(@RequestParam("bookValue")Double bookValue,@ModelAttribute("book") Book book,Model model) { System.out.println(book); return "hello"; } @ModelAttribute public void prePro(Map map) { Book book = new Book(); book.setBookName("西游记"); map.put("book", book);// Book book 没有注解的话,参数类型首字母小写找 map.put("haha", book);// @ModelAttribute("book") Book book //ModelAttribute 找不到 去@SessionAttribute找 找不到就报异常 //如果没有SessionAttribute 利用反射来获取book初始化对象 System.out.println("ModelAttribute....."); } }
- 利用名字来赋值
- 通过类名去找
- 利用sessionAttribute去找
- 利用返回值找
@ModelAttribute标注的方***提前运行并把方法的运行结果放在隐含模型中
放的时候使用一个key:
- @如果 @ModelAttribute("book")指定了,就用指定的book
- 如果没指定就用返回值类型的首字母小写作为key
@SessionAttributes 最好不使用 不然会报错
- 为了避免可能引发的异常 保证有两点
- 要么隐含模型中有@SessionAttribute标注的属性
- 要么Session中有