【5】Spring源码-SpringMVC

6. SpringMVC

SpringMVC的实现原理是通过servlet拦截所有URL来达到控制的目的。SpringMVC是基于Servlet的实现。真正的逻辑实现是在DispatcherServlet中进行的,DispatcherServlet继承了httpServlet,httpServlet继承了Servlet。httpServlet重写service方法,根据请求方法调用doGet,doPost等。Servlet接到请求就调用service方法。

  1. 对于一个web应用,其部署在web容器中,web容器提供其一个全局的上下文环境,这个上下文就是ServletContext,其为后面的spring IoC容器提供宿主环境;

  2. 在web.xml中会提供有contextLoaderListener。在web容器启动时,会触发容器初始化事件,此时contextLoaderListener会监听到这个事件,其contextInitialized方法会被调用,在这个方法中,spring会初始化一个启动上下文,这个上下文被称为根上下文,即WebApplicationContext,这是一个接口类,确切的说,其实际的实现类是XmlWebApplicationContext。这个就是spring的IoC容器,其对应的Bean定义的配置由web.xml中的context-param标签指定。在这个IoC容器初始化完毕后,spring以WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE为属性Key,将其存储到ServletContext中,便于获取;

  3. contextLoaderListener监听器初始化完毕后,开始初始化web.xml中配置的Servlet,这个servlet可以配置多个(一个web应用可以有多个DispatcherServlet),以最常见的DispatcherServlet为例,这个servlet实际上是一个标准的前端控制器,用以转发、匹配、处理每个servlet请求。DispatcherServlet上下文在初始化的时候会建立自己的IoC上下文,用以持有spring mvc相关的bean。在建立DispatcherServlet自己的IoC上下文时,会利用WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE先从ServletContext中获取之前的根上下文(即WebApplicationContext)作为自己上下文的parent上下文。有了这个parent上下文之后,再初始化自己持有的上下文。这个DispatcherServlet初始化自己上下文的工作在其initStrategies方法中可以看到,大概的工作就是初始化处理器映射、视图解析等。这个servlet自己持有的上下文默认实现类也是xmlWebApplicationContext。初始化完毕后,spring以与servlet的名字相关(此处不是简单的以servlet名为Key,而是通过一些转换)的属性为属性Key,也将其存到ServletContext中,以便后续使用。这样每个servlet就持有自己的上下文,即拥有自己独立的bean空间,同时各个servlet共享相同的父bean,即根上下文(第2步中初始化的上下文)定义的那些bean。

6.1 DispatcherServlet的初始化

DispatcherServlet的初始化过程主要是:

  1. 通过将当前的servlet类型实例转换为BeanWrapper类型实例,以便使用Spring中提供的注入功能进行对应属性的注入。

  2. 初始化WebApplicationContext,DispatcherServlet中会持有一个WebApplicationContext对象的成员变量(DispatcherServlet中持有的Ioc容器中有Controller、Spring默认的各种HandlerMapping、HandlerAdapter):
    a. 初始化WebApplicationContext,并设置其parent ApplicationContext。
    b. 调用WebApplicationContext的refresh方法进行配置文件加载。
    c. 调用DispatcherServlet重写的OnRefresh方法,初始化各种:

  • initMultipartResolver:用于处理文件上传的类,使用applicationContext.getBean()方法获取,并传入DispatcherServlet的一个成员变量中

  • initLocaleResolver:处理国际化问题,也是使用applicationContext.getBean()方法获取,如果拿不到则使用默认的策略(DefaultStrategy:在DispatcherServlet有静态代码块初始化,从文件中读取)

  • initThemeResolver:处理网站的主题资源,也是使用applicationContext.getBean()方法获取,如果拿不到则使用默认的策略

  • initHandlerMappings:当客户端发出Request时DispatcherServlet会将Request提交给HandlerMapping,然后HandlerMapping根据Web Application Context的配置来回传给DispatcherServlet相应的Controller。
    可以为DispatcherServlet提供多个Handler Mapping供其使用。DispatcherServlet在选用HandlerMapping的过程中,将根据我们所指定的一系列HandlerMapping的优先级进行排序(@Order注解),然后优先使用优先级在前的HandlerMapping。如果当前的HandlerMapping能够返回可用的Handler,DispatcherServlet则使用当前返回的Handler进行Web请求的处理,而不再继续询问其他的HandlerMapping。否则,DispatcherServlet将继续按照各个HandlerMapping的优先级进行询问,直到获取一个可用的Handler为止。也是使用applicationContext.getBean()方法获取,如果拿不到则使用默认的策略可以有多个HandlerMapping。

  • initHandlerAdapters:作为总控制器的派遣器servlet通过处理器映射得到处理器后,会轮询处理器适配器模块,查找能够处理当前HTTP请求的处理器适配器的实现,处理器适配器模块根据处理器映射返回的处理器类型,例如简单的控制器类型、注解控制器类型或者远程调用处理器类型,来选择某一个适当的处理器适配器的实现,从而适配当前的HTTP请求。
    也是使用applicationContext.getBean()方法获取,如果拿不到则使用默认的策略,可以有多个HandlerAdapter。

  • initHandlerExceptionResolvers:异常处理。也是使用applicationContext.getBean()方法获取,如果拿不到则使用默认的策略,可以有多个HandlerExceptionResolver。

  • initRequestToViewNameTranslator:Controller处理器方法没有返回一个View对象或逻辑视图名称,并且在该方法中没有直接往response的输出流里面写数据的时候,Spring就会采用约定好的方式提供一个逻辑视图名称。也是使用applicationContext.getBean()方法获取,如果拿不到则使用默认的策略。

  • initViewResolvers:在SpringMVC中,当Controller将请求处理结果放入到ModelAndView中以后, DispatcherServlet会根据ModelAndView选择合适的视图View进行渲染。ViewResolver接口定义了resolverViewName方法,根据viewName创建合适类型的View实现。可以配置JSP相关的viewResolver。
    也是使用applicationContext.getBean()方法获取,如果拿不到则使用默认的策略,可以有多个ViewResolver。

  • initFlashMapManager:SpringMVC Flash attributes提供了一个请求存储属性,可供其他请求使用。在使用重定向时候非常必要,例如Post/Redirect/Get模式。Flash attributes在重定向之前暂存(就像存在session中)以便重定向之后还能使用,并立即删除。默认开启。也是使用applicationContext.getBean()方法获取,如果拿不到则使用默认的策略。

// DispatcherServlet
protected void onRefresh(ApplicationContext context) {
   initStrategies(context);
}

protected void initStrategies(ApplicationContext context) {
   initMultipartResolver(context);
   initLocaleResolver(context);
   initThemeResolver(context);
   initHandlerMappings(context);
   initHandlerAdapters(context);
   initHandlerExceptionResolvers(context);
   initRequestToViewNameTranslator(context);
   initViewResolvers(context);
   initFlashMapManager(context);
}

6.2 DispatcherServlet的执行逻辑

请求->调用HttpServlet的doGet、doPost方法->DispatcherServlet的doService->DispatcherServlet的doDispatch
doDispatch中的逻辑:

  1. 检查是否为multipart请求,如果是则转换为MultipartHTTPServletRequest;

  2. 根据Request信息在HandlerMappings中寻找对应的handler;
    a. 变量HandlerMappings中的所有hanlder,可以根据url查找匹配相应的handler;
        i. 匹配时会将用户输入的url去除多余的”/“,如”/test//a///b”变成”/test/a/b"
        ii. RequestMapping(“/test/a/b”)在匹配时也会在最后加上”/“,保证能更”test/a/b/“匹配上
    b.将查找到的handler加入到拦截器链中;
    c. HandlerMapping中包含对应的controller和方法,启动时会将controller中的方法注入RequestMappingHandlerMapping中;
        i. Spring启动时配置好相应的HandlerMapping

    image.png

        ii. RequestMappingHandlerMapping中会注入使用@RequestMapping注解的url映射关系
    image.png

    d. handler是一个封装了Controller和具体要调用到Controller中的某个method的类(HandlerMethod),包含url到Controller到method的映射关系;

  3. 根据当前handler寻找对应的HandlerAdapter;
    a. 遍历所有的HandlerAdapter;
    b. 调用HandlerAdapter的supports方法判断是否匹配,supports方法实际上就是判断handler的类型(如SimpleControllerHandlerAdapter就是判断handler是否为Controller类型);
    c. HandlerAdapter主要做一些校验和准备工作(处理方法参数、相关注解、数据绑定、消息转换、返回值、调用视图解析器等等,如加了@ResponseBody就不会返回视图),并调用handler的反射执行方法;

  4. 处理lastModified缓存;

  5. 调用拦截器preHandler方法;

  6. 执行handler(调用HandlerAdapter.handle方法,该方法把具体处理逻辑委托给handler)并返回视图ModelAndView;
    a. @Controller注解由这个处理:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
    b. 最终用拿到Controller的bean,以及对应要执行的method,利用反射调用method;
    c. ModelAndView,view为要跳转的页面,model为注入的信息;


    image.png
  7. 调用拦截器的postHandler方法

  8. 处理页面跳转(redirect:xx, forward:xx),并将Model中的属性设置到request中;

6.3 父子容器

  1. DispatcherServlet中持有一个Ioc容器,其父容器为Spring的根Ioc容器,DispatcherServlet可以有多个,他们持有的多个Ioc容器均为同一个Spring跟Ioc容器。

  2. 使用这种父子容器的主要原因在于:
    a. 解耦:
    i. 在J2EE三层架构中,在service层我们一般使用spring框架, 而在web层则有多种选择,如spring mvc、struts等。因此,通常对于web层我们会使用单独的配置文件。如果我们将父子容器都配置与同一个applicationContext.xml中,那么我们想要将spring mvc替换为structs,还需要修改applicationContext.xml,而分为两种配置则无需修改applicationContext.xml。
    ii. 事实上,如果你的项目确定了只使用spring和spring mvc的话,你甚至可以将service 、dao、web层的bean都放到spring-servlet.xml中进行配置,并不是一定要将service、dao层的配置单独放到applicationContext.xml中,然后使用ContextLoaderListener来加载。在这种情况下,就没有了Root WebApplicationContext,只有Servlet WebApplicationContext。
    iii. 如果在子容器中取不到bean,则会去父容器中取。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270